Commit 1720fab6 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Add `aria-label` attribute to Annotation `article` elements

parent 739efa8b
import { Actions, Spinner } from '@hypothesis/frontend-shared';
import classnames from 'classnames';
import { useMemo } from 'preact/hooks';
import { useSidebarStore } from '../../store';
import { isOrphan, isSaved, quote } from '../../helpers/annotation-metadata';
import {
annotationRole,
isOrphan,
isSaved,
quote,
} from '../../helpers/annotation-metadata';
import { annotationDisplayName } from '../../helpers/annotation-user';
import { withServices } from '../../service-context';
import AnnotationActionBar from './AnnotationActionBar';
......@@ -84,8 +91,16 @@ function Annotation({
}
};
const authorName = useMemo(
() => annotationDisplayName(annotation, store),
[annotation, store]
);
return (
<article className="space-y-4">
<article
className="space-y-4"
aria-label={`${annotationRole(annotation)} by ${authorName}`}
>
<AnnotationHeader
annotation={annotation}
isEditing={isEditing}
......
......@@ -10,6 +10,7 @@ import Annotation, { $imports } from '../Annotation';
describe('Annotation', () => {
// Dependency Mocks
let fakeMetadata;
let fakeAnnotationUser;
// Injected dependency mocks
let fakeAnnotationsService;
......@@ -43,7 +44,12 @@ describe('Annotation', () => {
save: sinon.stub().resolves(),
};
fakeAnnotationUser = {
annotationDisplayName: sinon.stub().returns('Richard Lionheart'),
};
fakeMetadata = {
annotationRole: sinon.stub().returns('Annotation'),
quote: sinon.stub(),
};
......@@ -58,6 +64,7 @@ describe('Annotation', () => {
$imports.$mock(mockImportedComponents());
$imports.$mock({
'../../helpers/annotation-metadata': fakeMetadata,
'../../helpers/annotation-user': fakeAnnotationUser,
'../../store': { useSidebarStore: () => fakeStore },
});
});
......@@ -66,6 +73,17 @@ describe('Annotation', () => {
$imports.$restore();
});
describe('annotation accessibility (ARIA) attributes', () => {
it('should add an `aria-label` composed of annotation type and author name', () => {
const wrapper = createComponent();
assert.equal(
wrapper.find('article').props()['aria-label'],
'Annotation by Richard Lionheart'
);
});
});
describe('annotation quote', () => {
it('renders quote if annotation has a quote', () => {
fakeMetadata.quote.returns('quote');
......
......@@ -276,6 +276,22 @@ export function isAnnotation(annotation) {
return !!(hasSelector(annotation) && !isOrphan(annotation));
}
/**
* Return a human-readable string describing the annotation's role.
*
* @param {Annotation} annotation
*/
export function annotationRole(annotation) {
if (isReply(annotation)) {
return 'Reply';
} else if (isHighlight(annotation)) {
return 'Highlight';
} else if (isPageNote(annotation)) {
return 'Page note';
}
return 'Annotation';
}
/** Return a numeric key that can be used to sort annotations by location.
*
* @param {Annotation} annotation
......
......@@ -272,7 +272,7 @@ describe('sidebar/helpers/annotation-metadata', () => {
});
});
describe('.isHighlight', () => {
describe('isHighlight', () => {
[
{
annotation: fixtures.newEmptyAnnotation(),
......@@ -373,6 +373,47 @@ describe('sidebar/helpers/annotation-metadata', () => {
});
});
describe('annotationRole', () => {
it('correctly identifies the role of an annotation', () => {
// An annotation needs a `selector` or else it will be identified as a
// 'Page note'
const annotationAnnotation = {
...fixtures.newAnnotation(),
target: [{ source: 'source', selector: [] }],
};
const highlightAnnotation = fixtures.oldHighlight();
const pageNoteAnnotation = fixtures.newPageNote();
// If an annotation is a reply of any sort, that will supersede.
// e.g. the label for a page note that is also a reply is "Reply"
// In practice, highlights are never replies.
const replyAnnotations = [
{ ...annotationAnnotation, references: ['parent_annotation_id'] },
{ ...highlightAnnotation, references: ['parent_annotation_id'] },
{ ...pageNoteAnnotation, references: ['parent_annotation_id'] },
fixtures.oldReply(),
];
assert.equal(
annotationMetadata.annotationRole(annotationAnnotation),
'Annotation'
);
assert.equal(
annotationMetadata.annotationRole(highlightAnnotation),
'Highlight'
);
assert.equal(
annotationMetadata.annotationRole(pageNoteAnnotation),
'Page note'
);
replyAnnotations.forEach(reply => {
assert.equal(annotationMetadata.annotationRole(reply), 'Reply');
});
});
});
describe('isPublic', () => {
it('returns true if an annotation is shared within a group', () => {
assert.isTrue(annotationMetadata.isPublic(fixtures.publicAnnotation()));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment