diff --git a/packages/components/src/CommentItem/CommentItem.test.tsx b/packages/components/src/CommentItem/CommentItem.test.tsx index 53def4fb0d..b17dc2b3b7 100644 --- a/packages/components/src/CommentItem/CommentItem.test.tsx +++ b/packages/components/src/CommentItem/CommentItem.test.tsx @@ -7,10 +7,20 @@ import { Default } from './CommentItem.stories' import type { IProps } from './CommentItem' -describe('CommentList', () => { +describe('CommentItem', () => { it('shows the avatar', () => { const { getByTestId } = render() expect(getByTestId('commentAvatar')).toBeInTheDocument() + expect( + ( + getByTestId('commentAvatar').firstChild as HTMLImageElement + ).getAttribute('alt'), + ).not.empty + }) + it('shows the comment text', () => { + const { getByTestId } = render() + + expect(getByTestId('commentText')).toBeInTheDocument() }) }) diff --git a/packages/components/src/CommentItem/CommentItem.tsx b/packages/components/src/CommentItem/CommentItem.tsx index 5beb7cc60c..e96cbb1d42 100644 --- a/packages/components/src/CommentItem/CommentItem.tsx +++ b/packages/components/src/CommentItem/CommentItem.tsx @@ -91,6 +91,11 @@ export const CommentItem = (props: IProps) => { width: ['30px', '50px'], height: ['30px', '50px'], }} + alt={ + creatorName + ? `Avatar of ${creatorName}` + : 'Avatar of comment author' + } /> @@ -155,6 +160,7 @@ export const CommentItem = (props: IProps) => { { onClick={() => { triggerLightbox() }} - alt={activeImage.name} + alt={activeImage.alt ?? activeImage.name} crossOrigin="" /> {showNextPrevButton ? ( @@ -208,7 +209,7 @@ export const ImageGallery = (props: ImageGalleryProps) => { loading="lazy" src={image.thumbnailUrl} key={index} - alt={image.name} + alt={image.alt ?? image.name} sx={{ width: 100, height: 67, diff --git a/src/models/howto.models.tsx b/src/models/howto.models.tsx index afadc8d3a6..ea217e7faf 100644 --- a/src/models/howto.models.tsx +++ b/src/models/howto.models.tsx @@ -15,6 +15,7 @@ export interface IHowto extends IHowtoFormInput { _createdBy: string _deleted: boolean cover_image?: IUploadedFileMeta + cover_image_alt?: string fileLink?: string total_downloads?: number latestCommentDate?: string | undefined @@ -54,6 +55,7 @@ export interface IHowtoFormInput extends IModerable, ISharedFeatures { category?: ICategory // NOTE cover image input starts as convertedFileMeta but is transformed on upload cover_image?: IUploadedFileMeta | IConvertedFileMeta + cover_image_alt?: string // Added to be able to recover on edit by admin creatorCountry?: string totalComments?: number diff --git a/src/pages/Howto/Content/Common/Howto.form.tsx b/src/pages/Howto/Content/Common/Howto.form.tsx index eb450cd627..a70bed3286 100644 --- a/src/pages/Howto/Content/Common/Howto.form.tsx +++ b/src/pages/Howto/Content/Common/Howto.form.tsx @@ -23,6 +23,7 @@ import { HowtoButtonPublish } from './HowtoButtonPublish' import { HowtoErrors } from './HowtoErrors' import { HowtoFieldCategory } from './HowtoFieldCategory' import { HowtoFieldCoverImage } from './HowtoFieldCoverImage' +import { HowtoFieldCoverImageAlt } from './HowtoFieldCoverImageAlt' import { HowtoFieldDescription } from './HowtoFieldDescription' import { HowtoFieldDifficulty } from './HowtoFieldDifficulty' import { HowtoFieldFiles } from './HowtoFieldFiles' @@ -218,6 +219,7 @@ export const HowtoForm = observer((props: IProps) => { data-cy={'intro-cover'} > + diff --git a/src/pages/Howto/Content/Common/HowtoFieldCoverImageAlt.tsx b/src/pages/Howto/Content/Common/HowtoFieldCoverImageAlt.tsx new file mode 100644 index 0000000000..3a12f8b43a --- /dev/null +++ b/src/pages/Howto/Content/Common/HowtoFieldCoverImageAlt.tsx @@ -0,0 +1,27 @@ +import { Field } from 'react-final-form' +import { FieldInput } from 'oa-components' +import { FormFieldWrapper } from 'src/pages/Howto/Content/Common/FormFieldWrapper' +import { Box, Text } from 'theme-ui' + +import { intro } from '../../labels' + +export const HowtoFieldCoverImageAlt = () => { + const { description, placeholder, title } = intro.cover_image_alt + const name = 'cover_image_alt' + + return ( + + + + + + {description} + + + ) +} diff --git a/src/pages/Howto/Content/Howto/HowtoDescription/HowtoDescription.tsx b/src/pages/Howto/Content/Howto/HowtoDescription/HowtoDescription.tsx index 6f860e49b8..4c28c4de72 100644 --- a/src/pages/Howto/Content/Howto/HowtoDescription/HowtoDescription.tsx +++ b/src/pages/Howto/Content/Howto/HowtoDescription/HowtoDescription.tsx @@ -269,7 +269,7 @@ const HowtoDescription = ({ howto, loggedInUser, ...props }: IProps) => { width: '100%', }} crossOrigin="" - alt="how-to cover" + alt={howto.cover_image_alt ?? 'how-to cover'} /> )} diff --git a/src/pages/Howto/Content/Howto/Step/Step.tsx b/src/pages/Howto/Content/Howto/Step/Step.tsx index e0a9e0e5c7..8dc2f37b3b 100644 --- a/src/pages/Howto/Content/Howto/Step/Step.tsx +++ b/src/pages/Howto/Content/Howto/Step/Step.tsx @@ -82,7 +82,12 @@ const Step = (props: IProps) => { ) : step.images ? ( ) : null} diff --git a/src/pages/Howto/Content/HowtoList/HowToCard.tsx b/src/pages/Howto/Content/HowtoList/HowToCard.tsx index ff7f6bfcb6..ed3e138e40 100644 --- a/src/pages/Howto/Content/HowtoList/HowToCard.tsx +++ b/src/pages/Howto/Content/HowtoList/HowToCard.tsx @@ -43,6 +43,7 @@ export const HowToCard = ({ howto }: IProps) => { width: 500, })} crossOrigin="" + alt={howto.cover_image_alt ?? `Cover image of ${howto.title}`} /> diff --git a/src/pages/Howto/labels.ts b/src/pages/Howto/labels.ts index 2bcf8b91e0..b798fca587 100644 --- a/src/pages/Howto/labels.ts +++ b/src/pages/Howto/labels.ts @@ -52,6 +52,12 @@ export const intro: ILabels = { description: 'This image should be landscape. We advise 1280x960px', title: 'Cover image', }, + cover_image_alt: { + description: + 'This is the alternative text that is read out if the cover image is not available', + placeholder: 'People working on a house', + title: 'Cover image alt text', + }, description: { description: `Provide a short introduction (max ${HOWTO_MAX_LENGTH} characters)`, title: 'Short description', diff --git a/src/pages/Research/Content/ResearchListItem.tsx b/src/pages/Research/Content/ResearchListItem.tsx index 06ac728600..99c472e0a1 100644 --- a/src/pages/Research/Content/ResearchListItem.tsx +++ b/src/pages/Research/Content/ResearchListItem.tsx @@ -76,6 +76,7 @@ const ResearchListItem = ({ item }: IProps) => { src={cdnImageUrl(getItemThumbnail(item), { width: 125, })} + alt={`Thumbnail of ${item.title}`} crossOrigin="" /> diff --git a/src/stores/Howto/howto.store.tsx b/src/stores/Howto/howto.store.tsx index 147023bfa7..9f7a2d4108 100644 --- a/src/stores/Howto/howto.store.tsx +++ b/src/stores/Howto/howto.store.tsx @@ -325,6 +325,7 @@ export class HowtoStore extends ModuleStore { const slug = await this.setSlug(values) const previousSlugs = this.setPreviousSlugs(values, slug) const total_downloads = values['total_downloads'] ?? 0 + const cover_image_alt = values.cover_image_alt ?? '' const keywords = getKeywords(values.title + ' ' + values.description) keywords.push(_createdBy) @@ -346,6 +347,7 @@ export class HowtoStore extends ModuleStore { steps, title, keywords, + cover_image_alt, ...(latestCommentDate ? { latestCommentDate } : {}), ...(files ? { total_downloads } : {}), ...(category ? { category } : {}), diff --git a/src/utils/formatImageListForGallery.ts b/src/utils/formatImageListForGallery.ts index ba2ceeca22..296e5dab9c 100644 --- a/src/utils/formatImageListForGallery.ts +++ b/src/utils/formatImageListForGallery.ts @@ -5,6 +5,7 @@ import type { IConvertedFileMeta } from 'src/types' export const formatImagesForGallery = ( imageList: (IUploadedFileMeta | File | IConvertedFileMeta | null)[], + altPrefix?: string, ) => { if (!imageList) { return [] @@ -13,10 +14,11 @@ export const formatImagesForGallery = ( return imageList .filter(Boolean) .filter((i: any) => !!i?.downloadUrl) - .map((i: any) => ({ - downloadUrl: i.downloadUrl, - thumbnailUrl: cdnImageUrl(i.downloadUrl, { + .map((image: any, index: number) => ({ + downloadUrl: image.downloadUrl, + thumbnailUrl: cdnImageUrl(image.downloadUrl, { width: 150, }), + alt: `${altPrefix ? altPrefix + ' ' : ''}Gallery image ${index + 1}`, })) }