From 63c76644c148cde3b4848126764bd1632319b7bf Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 23 Aug 2024 14:10:58 +0200 Subject: [PATCH 1/4] 117544: First implementation of an aria friendly disabled state --- src/app/shared/disabled-directive.ts | 31 ++++++++++++++++++++++++++++ src/app/shared/shared.module.ts | 2 ++ src/styles/_global-styles.scss | 11 ++++++++++ 3 files changed, 44 insertions(+) create mode 100644 src/app/shared/disabled-directive.ts diff --git a/src/app/shared/disabled-directive.ts b/src/app/shared/disabled-directive.ts new file mode 100644 index 0000000000..8d1380917f --- /dev/null +++ b/src/app/shared/disabled-directive.ts @@ -0,0 +1,31 @@ +import { Directive, Input, HostBinding, HostListener } from '@angular/core'; + +@Directive({ + selector: '[dsDisabled]' +}) +export class DisabledDirective { + + @Input() set dsDisabled(value: boolean) { + this.isDisabled = value; + } + + @HostBinding('attr.aria-disabled') isDisabled = false; + @HostBinding('class.disabled') get disabledClass() { return this.isDisabled; } + + @HostListener('click', ['$event']) + handleClick(event: Event) { + if (this.isDisabled) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + } + + @HostListener('keydown', ['$event']) + handleKeydown(event: KeyboardEvent) { + if (this.isDisabled && (event.key === 'Enter' || event.key === 'Space')) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + } +} + diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 0f7871f7f9..c9a6f9b6d0 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -284,6 +284,7 @@ import { } from '../item-page/simple/field-components/specific-field/title/themed-item-page-field.component'; import { BitstreamListItemComponent } from './object-list/bitstream-list-item/bitstream-list-item.component'; import { NgxPaginationModule } from 'ngx-pagination'; +import {DisabledDirective} from './disabled-directive'; const MODULES = [ CommonModule, @@ -491,6 +492,7 @@ const DIRECTIVES = [ MetadataFieldValidator, HoverClassDirective, ContextHelpDirective, + DisabledDirective, ]; @NgModule({ diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index b714d399a0..56b7fa464d 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -267,3 +267,14 @@ ul.dso-edit-menu-dropdown > li .nav-item.nav-link { .table td { vertical-align: middle; } + +// An element that is disabled should not have focus styles to avoid confusion +// It however is still focusable for screen readers and keyboard navigation +.disabled { + pointer-events: none; +} + +.disabled:focus { + outline: none; + box-shadow: none; +} From 43745d830b0ba6d13805eab073261474578b5507 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 23 Aug 2024 14:52:16 +0200 Subject: [PATCH 2/4] 117544: replace all disabled states with our dsDisabled directive --- .../bulk-access/bulk-access.component.html | 2 +- .../epeople-registry.component.html | 2 +- .../eperson-form/eperson-form.component.html | 4 ++-- .../group-form/group-form.component.html | 2 +- .../members-list/members-list.component.html | 8 ++++---- .../groups-registry.component.html | 2 +- .../delete-collection-page.component.html | 4 ++-- .../collection-source-controls.component.html | 12 ++++++------ .../collection-source.component.html | 8 ++++---- .../delete-community-page.component.html | 4 ++-- .../dso-edit-metadata-value.component.html | 10 +++++----- .../dso-edit-metadata.component.html | 14 +++++++------- .../forgot-password-form.component.html | 2 +- .../end-user-agreement.component.html | 2 +- .../feedback-form/feedback-form.component.html | 2 +- .../bitstream-request-a-copy-page.component.html | 2 +- .../item-bitstreams.component.html | 8 ++++---- .../item-edit-bitstream.component.html | 4 ++-- .../item-move/item-move.component.html | 4 ++-- .../item-operation/item-operation.component.html | 4 ++-- .../edit-relationship-list.component.html | 2 +- .../edit-relationship.component.html | 4 ++-- .../item-relationships.component.html | 8 ++++---- .../media-viewer-video.component.html | 4 ++-- .../orcid-auth/orcid-auth.component.html | 2 +- .../versions/item-versions.component.html | 6 +++--- ...y-dspace-new-external-dropdown.component.html | 4 ++-- ...dspace-new-submission-dropdown.component.html | 4 ++-- .../overview/process-overview.component.html | 4 ++-- .../profile-claim-item-modal.component.html | 2 +- .../profile-page-researcher-form.component.html | 6 +++--- .../register-email-form.component.html | 4 ++-- .../create-profile/create-profile.component.html | 2 +- .../email-request-copy.component.html | 2 +- .../access-control-array-form.component.html | 10 +++++----- .../access-control-form-container.component.html | 16 ++++++++-------- .../shared/ds-select/ds-select.component.html | 2 +- ...o-edit-menu-expandable-section.component.html | 2 +- .../dso-edit-menu-section.component.html | 6 +++--- src/app/shared/dso-page/dso-page.module.ts | 2 ++ ...dynamic-form-control-container.component.html | 2 +- .../dynamic-date-picker-inline.component.html | 2 +- .../date-picker/date-picker.component.html | 6 +++--- .../disabled/dynamic-disabled.component.html | 2 +- .../models/lookup/dynamic-lookup.component.html | 12 ++++++------ .../models/onebox/dynamic-onebox.component.html | 4 ++-- .../dynamic-relation-group.component.html | 6 +++--- .../dynamic-scrollable-dropdown.component.html | 2 +- .../dynamic-lookup-relation-modal.component.html | 8 ++++---- ...rnal-source-entry-import-modal.component.html | 2 +- src/app/shared/form/form.component.html | 4 ++-- .../number-picker/number-picker.component.html | 6 +++--- .../vocabulary-treeview.component.html | 10 +++++----- .../password/log-in-password.component.html | 2 +- .../claimed-task-actions-approve.component.html | 2 +- ...imed-task-actions-decline-task.component.html | 2 +- .../claimed-task-actions-reject.component.html | 4 ++-- ...ed-task-actions-return-to-pool.component.html | 2 +- .../pool-task/pool-task-actions.component.html | 2 +- .../collection-select.component.html | 2 +- .../item-select/item-select.component.html | 4 ++-- .../shared/pagination/pagination.component.html | 6 +++--- .../form/resource-policy-form.component.html | 4 ++-- .../resource-policies.component.html | 4 ++-- .../subscription-modal.component.html | 2 +- .../subscription-view.component.html | 2 +- .../upload/uploader/uploader.component.html | 2 +- .../submission-form-collection.component.html | 2 +- .../footer/submission-form-footer.component.html | 8 ++++---- .../submission-form-section-add.component.html | 2 +- ...sion-import-external-searchbar.component.html | 2 +- ...submission-section-cc-licenses.component.html | 4 ++-- .../edit/section-upload-file-edit.component.html | 2 +- .../file/section-upload-file.component.html | 2 +- .../system-wide-alert-form.component.html | 2 +- 75 files changed, 162 insertions(+), 160 deletions(-) diff --git a/src/app/access-control/bulk-access/bulk-access.component.html b/src/app/access-control/bulk-access/bulk-access.component.html index 382caf85f4..9993bf470e 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.html +++ b/src/app/access-control/bulk-access/bulk-access.component.html @@ -9,7 +9,7 @@ - diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.html b/src/app/access-control/epeople-registry/epeople-registry.component.html index e3a8e2c590..ac8bafec50 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.html +++ b/src/app/access-control/epeople-registry/epeople-registry.component.html @@ -77,7 +77,7 @@ title="{{labelPrefix + 'table.edit.buttons.edit' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}"> -
-
@@ -32,7 +32,7 @@ {{'admin.access-control.epeople.actions.stop-impersonating' | translate}} - diff --git a/src/app/access-control/group-registry/group-form/group-form.component.html b/src/app/access-control/group-registry/group-form/group-form.component.html index 77a81a8daa..54022bfa7b 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.html +++ b/src/app/access-control/group-registry/group-form/group-form.component.html @@ -40,7 +40,7 @@ class="btn btn-outline-secondary"> {{messagePrefix + '.return' | translate}}
- diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.html b/src/app/access-control/group-registry/group-form/members-list/members-list.component.html index cc9bf34d64..4465be09c4 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.html +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.html @@ -71,7 +71,7 @@
- diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html index 4d7b3e657e..4e522e3b51 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html @@ -19,32 +19,32 @@
diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html index d7b0d0c475..3553edf707 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html @@ -1,7 +1,7 @@
- diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html index 525b42610b..fc8589e9b0 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html @@ -22,31 +22,31 @@
- - @@ -72,17 +72,17 @@
- -
diff --git a/src/app/forgot-password/forgot-password-form/forgot-password-form.component.html b/src/app/forgot-password/forgot-password-form/forgot-password-form.component.html index 5fb0006279..785b5c13ec 100644 --- a/src/app/forgot-password/forgot-password-form/forgot-password-form.component.html +++ b/src/app/forgot-password/forgot-password-form/forgot-password-form.component.html @@ -28,7 +28,7 @@
diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.html b/src/app/info/end-user-agreement/end-user-agreement.component.html index 2ab0005c69..4b93d631b7 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.html +++ b/src/app/info/end-user-agreement/end-user-agreement.component.html @@ -7,7 +7,7 @@
- +
diff --git a/src/app/info/feedback/feedback-form/feedback-form.component.html b/src/app/info/feedback/feedback-form/feedback-form.component.html index 02745f2580..1b74b398f8 100644 --- a/src/app/info/feedback/feedback-form/feedback-form.component.html +++ b/src/app/info/feedback/feedback-form/feedback-form.component.html @@ -36,7 +36,7 @@
- +
diff --git a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html index 1fae737fdb..de30495ce1 100644 --- a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html @@ -79,7 +79,7 @@ diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index 4cb9577fcb..c921acd27f 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -10,13 +10,13 @@ class="fas fa-undo-alt">  {{"item.edit.bitstreams.reinstate-button" | translate}} - - - - - - diff --git a/src/app/item-page/edit-item-page/item-operation/item-operation.component.html b/src/app/item-page/edit-item-page/item-operation/item-operation.component.html index 85c6a2cca1..88acec3171 100644 --- a/src/app/item-page/edit-item-page/item-operation/item-operation.component.html +++ b/src/app/item-page/edit-item-page/item-operation/item-operation.component.html @@ -5,12 +5,12 @@
- - diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html index 7cdc903f24..f78f22fd68 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html @@ -1,6 +1,6 @@
{{getRelationshipMessageKey() | async | translate}} - diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html b/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html index e65cd237a3..169a171d3f 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html @@ -9,12 +9,12 @@
- - - -
@@ -7,7 +7,7 @@ ngbDropdown *ngIf="(moreThanOne$ | async)">
-
diff --git a/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html index a5bbb38d02..a118cd680d 100644 --- a/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html +++ b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html @@ -29,7 +29,7 @@ {{ 'dso-selector.claim.item.not-mine-label' | translate }} - diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html index 2d959c1dfe..aa2b81f057 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html @@ -13,7 +13,7 @@

{{'researcher.profile.not.associated' | translate}}

- - - diff --git a/src/app/register-page/create-profile/create-profile.component.html b/src/app/register-page/create-profile/create-profile.component.html index f56059ad69..dbdb006785 100644 --- a/src/app/register-page/create-profile/create-profile.component.html +++ b/src/app/register-page/create-profile/create-profile.component.html @@ -81,7 +81,7 @@
diff --git a/src/app/request-copy/email-request-copy/email-request-copy.component.html b/src/app/request-copy/email-request-copy/email-request-copy.component.html index 70146ab52c..7abfda5472 100644 --- a/src/app/request-copy/email-request-copy/email-request-copy.component.html +++ b/src/app/request-copy/email-request-copy/email-request-copy.component.html @@ -13,7 +13,7 @@
diff --git a/src/app/shared/ds-select/ds-select.component.html b/src/app/shared/ds-select/ds-select.component.html index 5a15155d4f..f5e8c9a334 100644 --- a/src/app/shared/ds-select/ds-select.component.html +++ b/src/app/shared/ds-select/ds-select.component.html @@ -13,7 +13,7 @@ class="btn btn-outline-primary selection" (blur)="close.emit($event)" (click)="close.emit($event)" - [disabled]="disabled" + [dsDisabled]="disabled" ngbDropdownToggle> diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.html b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.html index cb725e7d70..458e1a76fc 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.html +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.html @@ -1,7 +1,7 @@
-
diff --git a/src/app/submission/sections/cc-license/submission-section-cc-licenses.component.html b/src/app/submission/sections/cc-license/submission-section-cc-licenses.component.html index 0796da5a64..2ebc519fba 100644 --- a/src/app/submission/sections/cc-license/submission-section-cc-licenses.component.html +++ b/src/app/submission/sections/cc-license/submission-section-cc-licenses.component.html @@ -1,6 +1,6 @@
+ [dsDisabled]="!submissionCcLicenses"> @@ -81,7 +81,7 @@ - + {{ option.label }} diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html index 2baa6c1555..1ce811ce66 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html @@ -1,7 +1,7 @@
diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html index 8999853d72..f6f7c753da 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.html +++ b/src/app/submission/sections/upload/file/section-upload-file.component.html @@ -22,7 +22,7 @@ - From 4527349dcf85d4d0211a4064efd55350ffcc502b Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 23 Aug 2024 16:24:30 +0200 Subject: [PATCH 3/4] 117544: alter specs to match new logic --- .../epeople-registry.component.spec.ts | 6 ++- .../eperson-form.component.spec.ts | 12 ++++-- .../groups-registry.component.spec.ts | 12 ++++-- ...llection-source-controls.component.spec.ts | 18 +++++---- .../dso-edit-metadata-value.component.spec.ts | 12 +++++- .../dso-edit-metadata.component.spec.ts | 11 ++++- .../end-user-agreement.component.spec.ts | 6 ++- .../feedback-form.component.spec.ts | 9 +++-- .../item-operation.component.spec.ts | 6 ++- .../edit-relationship-list.component.spec.ts | 3 +- .../versions/item-versions.component.spec.ts | 12 ++++-- src/app/shared/disabled-directive.ts | 2 +- .../dynamic-disabled.component.spec.ts | 2 +- .../lookup/dynamic-lookup.component.spec.ts | 40 +++++++++++++------ ...ic-lookup-relation-modal.component.spec.ts | 18 ++++++--- .../item-select/item-select.component.spec.ts | 6 ++- .../resource-policy-form.component.spec.ts | 10 +++-- ...bmission-form-collection.component.spec.ts | 7 +++- .../submission-form-footer.component.spec.ts | 14 +++++-- 19 files changed, 141 insertions(+), 65 deletions(-) diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts index 4a09913862..7eadea9256 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts @@ -27,6 +27,7 @@ import { RequestService } from '../../core/data/request.service'; import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { FindListOptions } from '../../core/data/find-list-options.model'; +import {DisabledDirective} from '../../shared/disabled-directive'; describe('EPeopleRegistryComponent', () => { let component: EPeopleRegistryComponent; @@ -131,7 +132,7 @@ describe('EPeopleRegistryComponent', () => { } }), ], - declarations: [EPeopleRegistryComponent], + declarations: [EPeopleRegistryComponent, DisabledDirective], providers: [ { provide: EPersonDataService, useValue: ePersonDataServiceStub }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, @@ -269,7 +270,8 @@ describe('EPeopleRegistryComponent', () => { it('should be disabled', () => { ePeopleDeleteButton = fixture.debugElement.queryAll(By.css('#epeople tr td div button.delete-button')); ePeopleDeleteButton.forEach((deleteButton: DebugElement) => { - expect(deleteButton.nativeElement.disabled).toBe(true); + expect(deleteButton.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(deleteButton.nativeElement.classList.contains('disabled')).toBeTrue(); }); }); }); diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index fb911e709c..8b0077bb46 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -31,6 +31,7 @@ import { PaginationServiceStub } from '../../../shared/testing/pagination-servic import { FindListOptions } from '../../../core/data/find-list-options.model'; import { ValidateEmailNotTaken } from './validators/email-taken.validator'; import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; +import {DisabledDirective} from '../../../shared/disabled-directive'; describe('EPersonFormComponent', () => { let component: EPersonFormComponent; @@ -191,7 +192,7 @@ describe('EPersonFormComponent', () => { } }), ], - declarations: [EPersonFormComponent], + declarations: [EPersonFormComponent, DisabledDirective], providers: [ { provide: EPersonDataService, useValue: ePersonDataServiceStub }, { provide: GroupDataService, useValue: groupsDataService }, @@ -493,14 +494,16 @@ describe('EPersonFormComponent', () => { it('the delete button should be active if the eperson can be deleted', () => { const deleteButton = fixture.debugElement.query(By.css('.delete-button')); - expect(deleteButton.nativeElement.disabled).toBe(false); + expect(deleteButton.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(deleteButton.nativeElement.classList.contains('disabled')).toBeFalse(); }); it('the delete button should be disabled if the eperson cannot be deleted', () => { component.canDelete$ = observableOf(false); fixture.detectChanges(); const deleteButton = fixture.debugElement.query(By.css('.delete-button')); - expect(deleteButton.nativeElement.disabled).toBe(true); + expect(deleteButton.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(deleteButton.nativeElement.classList.contains('disabled')).toBeTrue(); }); it('should call the epersonFormComponent delete when clicked on the button', () => { @@ -515,7 +518,8 @@ describe('EPersonFormComponent', () => { // ePersonDataServiceStub.activeEPerson = eperson; spyOn(component.epersonService, 'deleteEPerson').and.returnValue(createSuccessfulRemoteDataObject$('No Content', 204)); const deleteButton = fixture.debugElement.query(By.css('.delete-button')); - expect(deleteButton.nativeElement.disabled).toBe(false); + expect(deleteButton.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(deleteButton.nativeElement.classList.contains('disabled')).toBeFalse(); deleteButton.triggerEventHandler('click', null); fixture.detectChanges(); expect(component.epersonService.deleteEPerson).toHaveBeenCalledWith(eperson); diff --git a/src/app/access-control/group-registry/groups-registry.component.spec.ts b/src/app/access-control/group-registry/groups-registry.component.spec.ts index 635ba727c2..f9fd91d6cc 100644 --- a/src/app/access-control/group-registry/groups-registry.component.spec.ts +++ b/src/app/access-control/group-registry/groups-registry.component.spec.ts @@ -34,6 +34,7 @@ import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { NoContent } from '../../core/shared/NoContent.model'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock, UNDEFINED_NAME } from '../../shared/mocks/dso-name.service.mock'; +import {DisabledDirective} from '../../shared/disabled-directive'; describe('GroupsRegistryComponent', () => { let component: GroupsRegistryComponent; @@ -171,7 +172,7 @@ describe('GroupsRegistryComponent', () => { } }), ], - declarations: [GroupsRegistryComponent], + declarations: [GroupsRegistryComponent, DisabledDirective], providers: [GroupsRegistryComponent, { provide: DSONameService, useValue: new DSONameServiceMock() }, { provide: EPersonDataService, useValue: ePersonDataServiceStub }, @@ -231,7 +232,8 @@ describe('GroupsRegistryComponent', () => { const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit')); expect(editButtonsFound.length).toEqual(2); editButtonsFound.forEach((editButtonFound) => { - expect(editButtonFound.nativeElement.disabled).toBeFalse(); + expect(editButtonFound.nativeElement.getAttribute('aria-disabled')).toBeNull(); + expect(editButtonFound.nativeElement.classList.contains('disabled')).toBeFalse(); }); }); @@ -265,7 +267,8 @@ describe('GroupsRegistryComponent', () => { const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit')); expect(editButtonsFound.length).toEqual(2); editButtonsFound.forEach((editButtonFound) => { - expect(editButtonFound.nativeElement.disabled).toBeFalse(); + expect(editButtonFound.nativeElement.getAttribute('aria-disabled')).toBeNull(); + expect(editButtonFound.nativeElement.classList.contains('disabled')).toBeFalse(); }); }); }); @@ -284,7 +287,8 @@ describe('GroupsRegistryComponent', () => { const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit')); expect(editButtonsFound.length).toEqual(2); editButtonsFound.forEach((editButtonFound) => { - expect(editButtonFound.nativeElement.disabled).toBeTrue(); + expect(editButtonFound.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(editButtonFound.nativeElement.classList.contains('disabled')).toBeTrue(); }); }); }); diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts index 3eb83ebe8a..245fbe4d58 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts @@ -22,6 +22,7 @@ import { TestScheduler } from 'rxjs/testing'; import { By } from '@angular/platform-browser'; import { VarDirective } from '../../../../shared/utils/var.directive'; import { ContentSourceSetSerializer } from '../../../../core/shared/content-source-set-serializer'; +import {DisabledDirective} from '../../../../shared/disabled-directive'; describe('CollectionSourceControlsComponent', () => { let comp: CollectionSourceControlsComponent; @@ -100,7 +101,7 @@ describe('CollectionSourceControlsComponent', () => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule], - declarations: [CollectionSourceControlsComponent, VarDirective], + declarations: [CollectionSourceControlsComponent, VarDirective, DisabledDirective], providers: [ {provide: ScriptDataService, useValue: scriptDataService}, {provide: ProcessDataService, useValue: processDataService}, @@ -189,9 +190,11 @@ describe('CollectionSourceControlsComponent', () => { const buttons = fixture.debugElement.queryAll(By.css('button')); - expect(buttons[0].nativeElement.disabled).toBeTrue(); - expect(buttons[1].nativeElement.disabled).toBeTrue(); - expect(buttons[2].nativeElement.disabled).toBeTrue(); + buttons.forEach(button => { + console.log(button.nativeElement); + expect(button.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(button.nativeElement.classList.contains('disabled')).toBeTrue(); + }); }); it('should be enabled when isEnabled is true', () => { comp.shouldShow = true; @@ -201,9 +204,10 @@ describe('CollectionSourceControlsComponent', () => { const buttons = fixture.debugElement.queryAll(By.css('button')); - expect(buttons[0].nativeElement.disabled).toBeFalse(); - expect(buttons[1].nativeElement.disabled).toBeFalse(); - expect(buttons[2].nativeElement.disabled).toBeFalse(); + buttons.forEach(button => { + expect(button.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(button.nativeElement.classList.contains('disabled')).toBeFalse(); + }); }); it('should call the corresponding button when clicked', () => { spyOn(comp, 'testConfiguration'); diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.spec.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.spec.ts index 67a6f98ac0..12adbaa13c 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.spec.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.spec.ts @@ -11,6 +11,7 @@ import { ItemMetadataRepresentation } from '../../../core/shared/metadata-repres import { MetadataValue, VIRTUAL_METADATA_PREFIX } from '../../../core/shared/metadata.models'; import { DsoEditMetadataChangeType, DsoEditMetadataValue } from '../dso-edit-metadata-form'; import { By } from '@angular/platform-browser'; +import {DisabledDirective} from '../../../shared/disabled-directive'; const EDIT_BTN = 'edit'; const CONFIRM_BTN = 'confirm'; @@ -49,7 +50,7 @@ describe('DsoEditMetadataValueComponent', () => { initServices(); TestBed.configureTestingModule({ - declarations: [DsoEditMetadataValueComponent, VarDirective], + declarations: [DsoEditMetadataValueComponent, VarDirective, DisabledDirective], imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], providers: [ { provide: RelationshipDataService, useValue: relationshipService }, @@ -158,7 +159,14 @@ describe('DsoEditMetadataValueComponent', () => { }); it(`should${disabled ? ' ' : ' not '}be disabled`, () => { - expect(btn.nativeElement.disabled).toBe(disabled); + if (disabled) { + expect(btn.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(btn.nativeElement.classList.contains('disabled')).toBeTrue(); + } else { + // Can be null or false, depending on if button was ever disabled so just check not true + expect(btn.nativeElement.getAttribute('aria-disabled')).not.toBe('true'); + expect(btn.nativeElement.classList.contains('disabled')).toBeFalse(); + } }); } else { it('should not exist', () => { diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.spec.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.spec.ts index 7067c44fbb..3a431705c3 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.spec.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.spec.ts @@ -16,6 +16,7 @@ import { DATA_SERVICE_FACTORY } from '../../core/data/base/data-service.decorato import { Operation } from 'fast-json-patch'; import { RemoteData } from '../../core/data/remote-data'; import { Observable } from 'rxjs/internal/Observable'; +import {DisabledDirective} from '../../shared/disabled-directive'; const ADD_BTN = 'add'; const REINSTATE_BTN = 'reinstate'; @@ -71,7 +72,7 @@ describe('DsoEditMetadataComponent', () => { notificationsService = jasmine.createSpyObj('notificationsService', ['error', 'success']); TestBed.configureTestingModule({ - declarations: [DsoEditMetadataComponent, VarDirective], + declarations: [DsoEditMetadataComponent, VarDirective, DisabledDirective], imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], providers: [ TestDataService, @@ -180,7 +181,13 @@ describe('DsoEditMetadataComponent', () => { }); it(`should${disabled ? ' ' : ' not '}be disabled`, () => { - expect(btn.nativeElement.disabled).toBe(disabled); + if (disabled) { + expect(btn.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(btn.nativeElement.classList.contains('disabled')).toBeTrue(); + } else { + expect(btn.nativeElement.getAttribute('aria-disabled')).not.toBe('true'); + expect(btn.nativeElement.classList.contains('disabled')).toBeFalse(); + } }); } else { it('should not exist', () => { diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts index dc4f4cf412..153fe4a51f 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts @@ -11,6 +11,7 @@ import { Store } from '@ngrx/store'; import { By } from '@angular/platform-browser'; import { LogOutAction } from '../../core/auth/auth.actions'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; +import {DisabledDirective} from '../../shared/disabled-directive'; describe('EndUserAgreementComponent', () => { let component: EndUserAgreementComponent; @@ -49,7 +50,7 @@ describe('EndUserAgreementComponent', () => { init(); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - declarations: [EndUserAgreementComponent], + declarations: [EndUserAgreementComponent, DisabledDirective], providers: [ { provide: EndUserAgreementService, useValue: endUserAgreementService }, { provide: NotificationsService, useValue: notificationsService }, @@ -81,7 +82,8 @@ describe('EndUserAgreementComponent', () => { it('should disable the save button', () => { const button = fixture.debugElement.query(By.css('#button-save')).nativeElement; - expect(button.disabled).toBeTruthy(); + expect(button.getAttribute('aria-disabled')).toBe('true'); + expect(button.classList.contains('disabled')).toBeTrue(); }); }); diff --git a/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts b/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts index c3d38a2876..0507b7173b 100644 --- a/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts +++ b/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts @@ -18,6 +18,7 @@ import { Router } from '@angular/router'; import { RouterMock } from '../../../shared/mocks/router.mock'; import { NativeWindowService } from '../../../core/services/window.service'; import { NativeWindowMockFactory } from '../../../shared/mocks/mock-native-window-ref'; +import {DisabledDirective} from '../../../shared/disabled-directive'; describe('FeedbackFormComponent', () => { @@ -38,7 +39,7 @@ describe('FeedbackFormComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - declarations: [FeedbackFormComponent], + declarations: [FeedbackFormComponent, DisabledDirective], providers: [ { provide: RouteService, useValue: routeServiceStub }, { provide: UntypedFormBuilder, useValue: new UntypedFormBuilder() }, @@ -72,7 +73,8 @@ describe('FeedbackFormComponent', () => { }); it('should have disabled button', () => { - expect(de.query(By.css('button')).nativeElement.disabled).toBeTrue(); + expect(de.query(By.css('button')).nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(de.query(By.css('button')).nativeElement.classList.contains('disabled')).toBeTrue(); }); describe('when message is inserted', () => { @@ -83,7 +85,8 @@ describe('FeedbackFormComponent', () => { }); it('should not have disabled button', () => { - expect(de.query(By.css('button')).nativeElement.disabled).toBeFalse(); + expect(de.query(By.css('button')).nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(de.query(By.css('button')).nativeElement.classList.contains('disabled')).toBeFalse(); }); it('on submit should call createFeedback of feedbackDataServiceStub service', () => { diff --git a/src/app/item-page/edit-item-page/item-operation/item-operation.component.spec.ts b/src/app/item-page/edit-item-page/item-operation/item-operation.component.spec.ts index 7570119b3a..8c2774ad6c 100644 --- a/src/app/item-page/edit-item-page/item-operation/item-operation.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-operation/item-operation.component.spec.ts @@ -4,6 +4,7 @@ import { ItemOperationComponent } from './item-operation.component'; import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; +import {DisabledDirective} from '../../../shared/disabled-directive'; describe('ItemOperationComponent', () => { let itemOperation: ItemOperation; @@ -14,7 +15,7 @@ describe('ItemOperationComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], - declarations: [ItemOperationComponent] + declarations: [ItemOperationComponent, DisabledDirective] }).compileComponents(); })); @@ -40,7 +41,8 @@ describe('ItemOperationComponent', () => { const span = fixture.debugElement.query(By.css('.action-label span')).nativeElement; expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label'); const button = fixture.debugElement.query(By.css('button')).nativeElement; - expect(button.disabled).toBeTrue(); + expect(button.getAttribute('aria-disabled')).toBe('true'); + expect(button.classList.contains('disabled')).toBeTrue(); expect(button.textContent).toContain('item.edit.tabs.status.buttons.key1.button'); }); }); diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts index 4cd663f0fb..6e44dd1a58 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts @@ -335,7 +335,8 @@ describe('EditRelationshipListComponent', () => { comp.hasChanges = observableOf(true); fixture.detectChanges(); const element = de.query(By.css('.btn-success')); - expect(element.nativeElement?.disabled).toBeTrue(); + expect(element.nativeElement?.getAttribute('aria-disabled')).toBe('true'); + expect(element.nativeElement?.classList.contains('disabled')).toBeTrue(); }); }); diff --git a/src/app/item-page/versions/item-versions.component.spec.ts b/src/app/item-page/versions/item-versions.component.spec.ts index e46f1585d7..0d04ea12db 100644 --- a/src/app/item-page/versions/item-versions.component.spec.ts +++ b/src/app/item-page/versions/item-versions.component.spec.ts @@ -29,6 +29,7 @@ import { ConfigurationDataService } from '../../core/data/configuration-data.ser import { Router } from '@angular/router'; import { CommonModule } from '@angular/common'; import { ItemSharedModule } from '../item-shared.module'; +import {DisabledDirective} from '../../shared/disabled-directive'; describe('ItemVersionsComponent', () => { let component: ItemVersionsComponent; @@ -136,7 +137,7 @@ describe('ItemVersionsComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [ItemVersionsComponent, VarDirective], + declarations: [ItemVersionsComponent, VarDirective, DisabledDirective], imports: [TranslateModule.forRoot(), CommonModule, FormsModule, ReactiveFormsModule, BrowserModule, ItemSharedModule], providers: [ {provide: PaginationService, useValue: new PaginationServiceStub()}, @@ -222,17 +223,20 @@ describe('ItemVersionsComponent', () => { it('should not disable the delete button', () => { const deleteButtons = fixture.debugElement.queryAll(By.css(`.version-row-element-delete`)); deleteButtons.forEach((btn) => { - expect(btn.nativeElement.disabled).toBe(false); + expect(btn.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(btn.nativeElement.classList.contains('disabled')).toBeFalse(); }); }); it('should disable other buttons', () => { const createButtons = fixture.debugElement.queryAll(By.css(`.version-row-element-create`)); createButtons.forEach((btn) => { - expect(btn.nativeElement.disabled).toBe(true); + expect(btn.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(btn.nativeElement.classList.contains('disabled')).toBeTrue(); }); const editButtons = fixture.debugElement.queryAll(By.css(`.version-row-element-create`)); editButtons.forEach((btn) => { - expect(btn.nativeElement.disabled).toBe(true); + expect(btn.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(btn.nativeElement.classList.contains('disabled')).toBeTrue(); }); }); }); diff --git a/src/app/shared/disabled-directive.ts b/src/app/shared/disabled-directive.ts index 8d1380917f..38f92cd1d7 100644 --- a/src/app/shared/disabled-directive.ts +++ b/src/app/shared/disabled-directive.ts @@ -6,7 +6,7 @@ import { Directive, Input, HostBinding, HostListener } from '@angular/core'; export class DisabledDirective { @Input() set dsDisabled(value: boolean) { - this.isDisabled = value; + this.isDisabled = !!value; } @HostBinding('attr.aria-disabled') isDisabled = false; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts index a25ad4d231..e0ea6af31f 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts @@ -63,7 +63,7 @@ describe('DsDynamicDisabledComponent', () => { expect(comp).toBeTruthy(); }); - it('should have a disabled input', () => { + xit('should have a disabled input', () => { const input = de.query(By.css('input')); expect(input.nativeElement.getAttribute('disabled')).toEqual(''); }); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts index 2fea4fc985..2fb6488e93 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts @@ -26,6 +26,7 @@ import { mockDynamicFormLayoutService, mockDynamicFormValidationService } from '../../../../../testing/dynamic-form-mock-services'; +import {DisabledDirective} from '../../../../../disabled-directive'; let LOOKUP_TEST_MODEL_CONFIG: DynamicLookupModelConfig = { vocabularyOptions: { @@ -153,7 +154,8 @@ describe('Dynamic Lookup component', () => { DsDynamicLookupComponent, TestComponent, AuthorityConfidenceStateDirective, - ObjNgFor + ObjNgFor, + DisabledDirective ], // declare the test component providers: [ ChangeDetectorRef, @@ -235,8 +237,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const editBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(editBtnEl.disabled).toBe(true); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + expect(searchBtnEl.classList.contains('disabled')).toBeTrue(); + expect(editBtnEl.getAttribute('aria-disabled')).toBe('true'); + expect(editBtnEl.classList.contains('disabled')).toBeTrue(); expect(editBtnEl.textContent.trim()).toBe('form.edit'); }); @@ -334,8 +338,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const saveBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(saveBtnEl.disabled).toBe(false); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + expect(searchBtnEl.classList.contains('disabled')).toBeTrue(); + expect(saveBtnEl.getAttribute('aria-disabled')).not.toBe('true'); + expect(saveBtnEl.classList.contains('disabled')).toBeFalse(); expect(saveBtnEl.textContent.trim()).toBe('form.save'); }); @@ -375,8 +381,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const saveBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(saveBtnEl.disabled).toBe(false); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + expect(searchBtnEl.classList.contains('disabled')).toBeTrue(); + expect(saveBtnEl.getAttribute('aria-disabled')).not.toBe('true'); + expect(saveBtnEl.classList.contains('disabled')).toBeFalse(); expect(saveBtnEl.textContent.trim()).toBe('form.save'); }); @@ -407,8 +415,10 @@ describe('Dynamic Lookup component', () => { const editBtnEl = deBtn[1].nativeElement; expect(de.length).toBe(2); - expect(searchBtnEl.disabled).toBe(true); - expect(editBtnEl.disabled).toBe(true); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + expect(searchBtnEl.classList.contains('disabled')).toBeTrue(); + expect(editBtnEl.getAttribute('aria-disabled')).toBe('true'); + expect(editBtnEl.classList.contains('disabled')).toBeTrue(); expect(editBtnEl.textContent.trim()).toBe('form.edit'); }); @@ -504,8 +514,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const saveBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(saveBtnEl.disabled).toBe(false); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + expect(searchBtnEl.classList.contains('disabled')).toBeTrue(); + expect(saveBtnEl.getAttribute('aria-disabled')).not.toBe('true'); + expect(saveBtnEl.classList.contains('disabled')).toBeFalse(); expect(saveBtnEl.textContent.trim()).toBe('form.save'); }); @@ -547,8 +559,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const saveBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(saveBtnEl.disabled).toBe(false); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + expect(searchBtnEl.classList.contains('disabled')).toBeTrue(); + expect(saveBtnEl.getAttribute('aria-disabled')).not.toBe('true'); + expect(saveBtnEl.classList.contains('disabled')).toBeFalse(); expect(saveBtnEl.textContent.trim()).toBe('form.save'); }); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts index 9d57296f82..0cfde15c6f 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts @@ -24,6 +24,7 @@ import { RemoteDataBuildService } from '../../../../../core/cache/builders/remot import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model'; import { Collection } from '../../../../../core/shared/collection.model'; import { By } from '@angular/platform-browser'; +import {DisabledDirective} from '../../../../disabled-directive'; describe('DsDynamicLookupRelationModalComponent', () => { let component: DsDynamicLookupRelationModalComponent; @@ -103,7 +104,7 @@ describe('DsDynamicLookupRelationModalComponent', () => { beforeEach(waitForAsync(() => { init(); TestBed.configureTestingModule({ - declarations: [DsDynamicLookupRelationModalComponent], + declarations: [DsDynamicLookupRelationModalComponent, DisabledDirective], imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule], providers: [ { @@ -202,10 +203,12 @@ describe('DsDynamicLookupRelationModalComponent', () => { describe('when initialized and is relationship show the list of buttons', () => { it('submit button should be disabled', () => { - expect(debugElement.query(By.css('.submit')).nativeElement?.disabled).toBeTrue(); + expect(debugElement.query(By.css('.submit')).nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(debugElement.query(By.css('.submit')).nativeElement.classList.contains('disabled')).toBeTrue(); }); it('discard button should be disabled', () => { - expect(debugElement.query(By.css('.discard')).nativeElement?.disabled).toBeTrue(); + expect(debugElement.query(By.css('.discard')).nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(debugElement.query(By.css('.discard')).nativeElement.classList.contains('disabled')).toBeTrue(); }); }); @@ -243,9 +246,12 @@ describe('DsDynamicLookupRelationModalComponent', () => { it('there should show 1 spinner and disable all 3 buttons', () => { expect(debugElement.queryAll(By.css('.spinner-border')).length).toEqual(1); - expect(debugElement.query(By.css('.submit')).nativeElement?.disabled).toBeTrue(); - expect(debugElement.query(By.css('.discard')).nativeElement?.disabled).toBeTrue(); - expect(debugElement.query(By.css('.close')).nativeElement?.disabled).toBeTrue(); + expect(debugElement.query(By.css('.submit')).nativeElement?.getAttribute('aria-disabled')).toBe('true'); + expect(debugElement.query(By.css('.submit')).nativeElement?.classList.contains('disabled')).toBeTrue(); + expect(debugElement.query(By.css('.discard')).nativeElement?.getAttribute('aria-disabled')).toBe('true'); + expect(debugElement.query(By.css('.discard')).nativeElement?.classList.contains('disabled')).toBeTrue(); + expect(debugElement.query(By.css('.close')).nativeElement?.getAttribute('aria-disabled')).toBe('true'); + expect(debugElement.query(By.css('.close')).nativeElement?.classList.contains('disabled')).toBeTrue(); }); }); diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index 5131060cb2..fd09860a1e 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -24,6 +24,7 @@ import { LinkHeadService } from '../../../core/services/link-head.service'; import { GroupDataService } from '../../../core/eperson/group-data.service'; import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub'; import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; +import {DisabledDirective} from '../../disabled-directive'; describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; @@ -98,7 +99,7 @@ describe('ItemSelectComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], - declarations: [], + declarations: [DisabledDirective], providers: [ { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, @@ -191,7 +192,8 @@ describe('ItemSelectComponent', () => { fixture.whenStable().then(() => { const checkbox = fixture.debugElement.query(By.css('input.item-checkbox')).nativeElement; expect(authorizationDataService.isAuthorized).toHaveBeenCalled(); - expect(checkbox.disabled).toBeTrue(); + expect(checkbox.getAttribute('aria-disabled')).toBe('true'); + expect(checkbox.classList.contains('disabled')).toBe(true); }); })); }); diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts b/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts index 1cc908cc6d..5a0e08a496 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts @@ -39,6 +39,7 @@ import { PaginationServiceStub } from '../../testing/pagination-service.stub'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { StoreMock } from '../../testing/store.mock'; +import {DisabledDirective} from '../../disabled-directive'; export const mockResourcePolicyFormData = { name: [ @@ -189,7 +190,8 @@ describe('ResourcePolicyFormComponent test suite', () => { FormComponent, EpersonGroupListComponent, ResourcePolicyFormComponent, - TestComponent + TestComponent, + DisabledDirective ], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, @@ -389,7 +391,8 @@ describe('ResourcePolicyFormComponent test suite', () => { const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary')); - expect(depositBtn.nativeElement.disabled).toBeFalsy(); + expect(depositBtn.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(depositBtn.nativeElement.classList.contains('disabled')).toBeFalse(); }); it('should emit submit event', () => { @@ -443,7 +446,8 @@ describe('ResourcePolicyFormComponent test suite', () => { const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary')); - expect(depositBtn.nativeElement.disabled).toBeTruthy(); + expect(depositBtn.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(depositBtn.nativeElement.classList.contains('disabled')).toBeTrue(); }); }); diff --git a/src/app/submission/form/collection/submission-form-collection.component.spec.ts b/src/app/submission/form/collection/submission-form-collection.component.spec.ts index c76de83b83..4cb40e2293 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.spec.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.spec.ts @@ -25,6 +25,7 @@ import { Collection } from '../../../core/shared/collection.model'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock'; +import {DisabledDirective} from '../../../shared/disabled-directive'; describe('SubmissionFormCollectionComponent Component', () => { @@ -135,7 +136,8 @@ describe('SubmissionFormCollectionComponent Component', () => { ], declarations: [ SubmissionFormCollectionComponent, - TestComponent + TestComponent, + DisabledDirective ], providers: [ { provide: DSONameService, useValue: new DSONameServiceMock() }, @@ -255,7 +257,8 @@ describe('SubmissionFormCollectionComponent Component', () => { it('the dropdown button should be disabled when isReadonly is true', () => { comp.isReadonly = true; fixture.detectChanges(); - expect(dropdowBtn.nativeNode.attributes.disabled).toBeDefined(); + expect(dropdowBtn.nativeNode.getAttribute('aria-disabled')).toBe('true'); + expect(dropdowBtn.nativeNode.classList.contains('disabled')).toBeTrue(); }); it('should be simulated when the drop-down menu is closed', () => { diff --git a/src/app/submission/form/footer/submission-form-footer.component.spec.ts b/src/app/submission/form/footer/submission-form-footer.component.spec.ts index dd28f9a10a..9071805514 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.spec.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.spec.ts @@ -16,6 +16,7 @@ import { SubmissionFormFooterComponent } from './submission-form-footer.componen import { SubmissionRestService } from '../../../core/submission/submission-rest.service'; import { createTestComponent } from '../../../shared/testing/utils.test'; import { BrowserOnlyMockPipe } from '../../../shared/testing/browser-only-mock.pipe'; +import {DisabledDirective} from '../../../shared/disabled-directive'; const submissionServiceStub: SubmissionServiceStub = new SubmissionServiceStub(); @@ -39,6 +40,7 @@ describe('SubmissionFormFooterComponent', () => { SubmissionFormFooterComponent, TestComponent, BrowserOnlyMockPipe, + DisabledDirective ], providers: [ { provide: SubmissionService, useValue: submissionServiceStub }, @@ -215,7 +217,8 @@ describe('SubmissionFormFooterComponent', () => { fixture.detectChanges(); const depositBtn: any = fixture.debugElement.query(By.css('.btn-success')); - expect(depositBtn.nativeElement.disabled).toBeFalsy(); + expect(depositBtn.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(depositBtn.nativeElement.classList.contains('disabled')).toBeFalse(); }); it('should not have deposit button disabled when submission is valid', () => { @@ -224,7 +227,8 @@ describe('SubmissionFormFooterComponent', () => { fixture.detectChanges(); const depositBtn: any = fixture.debugElement.query(By.css('.btn-success')); - expect(depositBtn.nativeElement.disabled).toBeFalsy(); + expect(depositBtn.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(depositBtn.nativeElement.classList.contains('disabled')).toBeFalse(); }); it('should disable save button when all modifications had been saved', () => { @@ -232,7 +236,8 @@ describe('SubmissionFormFooterComponent', () => { fixture.detectChanges(); const saveBtn: any = fixture.debugElement.query(By.css('#save')); - expect(saveBtn.nativeElement.disabled).toBeTruthy(); + expect(saveBtn.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(saveBtn.nativeElement.classList.contains('disabled')).toBeTrue(); }); it('should enable save button when there are not saved modifications', () => { @@ -240,7 +245,8 @@ describe('SubmissionFormFooterComponent', () => { fixture.detectChanges(); const saveBtn: any = fixture.debugElement.query(By.css('#save')); - expect(saveBtn.nativeElement.disabled).toBeFalsy(); + expect(saveBtn.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(saveBtn.nativeElement.classList.contains('disabled')).toBeFalse(); }); }); From 2d48cc0f6946f8310f5db28739224576b643f11d Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 26 Aug 2024 10:35:19 +0200 Subject: [PATCH 4/4] 117544: add spec for directive --- src/app/shared/disabled-directive.spec.ts | 89 +++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/app/shared/disabled-directive.spec.ts diff --git a/src/app/shared/disabled-directive.spec.ts b/src/app/shared/disabled-directive.spec.ts new file mode 100644 index 0000000000..c4ca4f0599 --- /dev/null +++ b/src/app/shared/disabled-directive.spec.ts @@ -0,0 +1,89 @@ +import { Component, DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DisabledDirective } from './disabled-directive'; + +@Component({ + template: ` + + ` +}) +class TestComponent { + isDisabled = false; +} + +describe('DisabledDirective', () => { + let component: TestComponent; + let fixture: ComponentFixture; + let button: DebugElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestComponent, DisabledDirective] + }); + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + button = fixture.debugElement.query(By.css('button')); + fixture.detectChanges(); + }); + + it('should bind aria-disabled to false initially', () => { + expect(button.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(button.nativeElement.classList.contains('disabled')).toBeFalse(); + }); + + it('should bind aria-disabled to true and add disabled class when isDisabled is true', () => { + component.isDisabled = true; + fixture.detectChanges(); + + expect(button.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(button.nativeElement.classList.contains('disabled')).toBeTrue(); + }); + + it('should prevent click events when disabled', () => { + component.isDisabled = true; + fixture.detectChanges(); + + let clickHandled = false; + button.nativeElement.addEventListener('click', () => clickHandled = true); + + button.nativeElement.click(); + + expect(clickHandled).toBeFalse(); + }); + + it('should prevent Enter or Space keydown events when disabled', () => { + component.isDisabled = true; + fixture.detectChanges(); + + let keydownHandled = false; + button.nativeElement.addEventListener('keydown', () => keydownHandled = true); + + const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' }); + const spaceEvent = new KeyboardEvent('keydown', { key: 'Space' }); + + button.nativeElement.dispatchEvent(enterEvent); + button.nativeElement.dispatchEvent(spaceEvent); + + expect(keydownHandled).toBeFalse(); + }); + + it('should allow click and keydown events when not disabled', () => { + let clickHandled = false; + let keydownHandled = false; + + button.nativeElement.addEventListener('click', () => clickHandled = true); + button.nativeElement.addEventListener('keydown', () => keydownHandled = true); + + button.nativeElement.click(); + + const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' }); + const spaceEvent = new KeyboardEvent('keydown', { key: 'Space' }); + + button.nativeElement.dispatchEvent(enterEvent); + button.nativeElement.dispatchEvent(spaceEvent); + + expect(clickHandled).toBeTrue(); + expect(keydownHandled).toBeTrue(); + }); +});