diff --git a/src/app/shared/file-download-link/file-download-link.component.html b/src/app/shared/file-download-link/file-download-link.component.html
index 51a18d7e6e1..db054c83073 100644
--- a/src/app/shared/file-download-link/file-download-link.component.html
+++ b/src/app/shared/file-download-link/file-download-link.component.html
@@ -1,5 +1,11 @@
-
-
+
+
diff --git a/src/app/shared/file-download-link/file-download-link.component.spec.ts b/src/app/shared/file-download-link/file-download-link.component.spec.ts
index b9c2815535e..6336230e661 100644
--- a/src/app/shared/file-download-link/file-download-link.component.spec.ts
+++ b/src/app/shared/file-download-link/file-download-link.component.spec.ts
@@ -9,6 +9,8 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/
import { cold, getTestScheduler } from 'jasmine-marbles';
import { Item } from '../../core/shared/item.model';
import { RouterLinkDirectiveStub } from '../testing/router-link-directive.stub';
+import { TranslateModule } from '@ngx-translate/core';
+import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
describe('FileDownloadLinkComponent', () => {
let component: FileDownloadLinkComponent;
@@ -16,6 +18,9 @@ describe('FileDownloadLinkComponent', () => {
let scheduler;
let authorizationService: AuthorizationDataService;
+ let dsoNameService: DSONameService;
+
+ const bitstreamName = 'Test bitstream name';
let bitstream: Bitstream;
let item: Item;
@@ -24,6 +29,9 @@ describe('FileDownloadLinkComponent', () => {
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: cold('-a', {a: true})
});
+ dsoNameService = jasmine.createSpyObj('dsoNameService', {
+ getName: bitstreamName
+ });
bitstream = Object.assign(new Bitstream(), {
uuid: 'bitstreamUuid',
_links: {
@@ -40,9 +48,12 @@ describe('FileDownloadLinkComponent', () => {
function initTestbed() {
TestBed.configureTestingModule({
+ imports: [
+ TranslateModule.forRoot(),
+ ],
declarations: [FileDownloadLinkComponent, RouterLinkDirectiveStub],
providers: [
- {provide: AuthorizationDataService, useValue: authorizationService},
+ {provide: AuthorizationDataService, useValue: authorizationService, provide: DSONameService, useValue: dsoNameService},
],
schemas: [NO_ERRORS_SCHEMA],
})
@@ -78,6 +89,18 @@ describe('FileDownloadLinkComponent', () => {
const lock = fixture.debugElement.query(By.css('.fa-lock'));
expect(lock).toBeNull();
});
+ it('should set an accessible aria-label on the download link containing the bitstream name', () => {
+ scheduler.flush();
+ fixture.detectChanges();
+ const link = fixture.debugElement.query(By.css('a'));
+ expect(link.nativeElement.getAttribute('aria-label')).toContain(bitstreamName);
+ });
+ it('should not include the restricted-bitstream text in the aria-label', () => {
+ scheduler.flush();
+ fixture.detectChanges();
+ const link = fixture.debugElement.query(By.css('a'));
+ expect(link.nativeElement.getAttribute('aria-label')).not.toContain('file-download-link.restricted');
+ });
});
// describe('when the user has no download rights but has the right to request a copy', () => {
// beforeEach(waitForAsync(() => {
@@ -139,6 +162,17 @@ describe('FileDownloadLinkComponent', () => {
const lock = fixture.debugElement.query(By.css('.fa-lock')).nativeElement;
expect(lock).toBeTruthy();
});
+ it('should mark the lock icon as decorative and expose the restricted state via the link\'s own aria-label', () => {
+ scheduler.flush();
+ fixture.detectChanges();
+ const lock = fixture.debugElement.query(By.css('.fa-lock')).nativeElement;
+ expect(lock.getAttribute('aria-hidden')).toBe('true');
+ // The restricted state must be part of the link's own aria-label: an element's
+ // aria-label overrides its accessible-name computation entirely, so a nested
+ // sr-only text node would never be announced (this is the bug the fix corrects).
+ const link = fixture.debugElement.query(By.css('a'));
+ expect(link.nativeElement.getAttribute('aria-label')).toContain('file-download-link.restricted');
+ });
});
});
});
diff --git a/src/app/shared/file-download-link/file-download-link.component.ts b/src/app/shared/file-download-link/file-download-link.component.ts
index 51d1c3a67cd..48417c2a641 100644
--- a/src/app/shared/file-download-link/file-download-link.component.ts
+++ b/src/app/shared/file-download-link/file-download-link.component.ts
@@ -1,5 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { Bitstream } from '../../core/shared/bitstream.model';
+import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { getBitstreamDownloadRoute } from '../../app-routing-paths';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
@@ -48,6 +49,7 @@ export class FileDownloadLinkComponent implements OnInit {
constructor(
private authorizationService: AuthorizationDataService,
+ public dsoNameService: DSONameService,
) {
}
@@ -77,4 +79,16 @@ export class FileDownloadLinkComponent implements OnInit {
queryParams: {}
};
}
+
+ /**
+ * Builds the download link's accessible name. When the bitstream is restricted, the
+ * restricted state is folded into this same string (rather than relying on a nested
+ * sr-only text node) because an element's own aria-label overrides its accessible name
+ * computation entirely - descendant text, including sr-only spans, is not announced
+ * alongside it.
+ */
+ getDownloadLinkAriaLabel(canDownload: boolean | null, downloadLabel: string, restrictedLabel: string): string {
+ const download = `${downloadLabel.trim()} ${this.dsoNameService.getName(this.bitstream)}`.trim();
+ return canDownload ? download : `${restrictedLabel.trim()}, ${download}`;
+ }
}
diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5
index 705b4bd7b1b..cbc67091f47 100644
--- a/src/assets/i18n/cs.json5
+++ b/src/assets/i18n/cs.json5
@@ -2134,9 +2134,10 @@
"error.validation.metadata.qualifier.max-length": "Toto pole nesmí obsahovat více než 64 znaků",
// "feed.description": "Syndication feed",
"feed.description": "Synchronizační kanál",
+ // "file-download-link.download": "Download",
+ "file-download-link.download": "Stáhnout",
// "file-download-link.restricted": "Restricted bitstream",
- // TODO New key - Add a translation
- "file-download-link.restricted": "Restricted bitstream",
+ "file-download-link.restricted": "Omezený soubor",
// "file-section.error.header": "Error obtaining files for this item",
"file-section.error.header": "Chyba při získávání souborů pro tento záznam",
// "footer.copyright": "copyright © 2002-{{ year }}",
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index e19e906d8ff..5f34da08959 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -1701,6 +1701,10 @@
"feed.description": "RSS feed",
+ "file-download-link.download": "Download",
+
+ "file-download-link.restricted": "Restricted bitstream",
+
"file-section.error.header": "Error obtaining files for this item",
"footer.copyright": "copyright © 2002-{{ year }}",