From 334efabef6bc8f203f9eb6db7b1b5c2997729f24 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 23 Jul 2026 22:45:26 +0000 Subject: [PATCH 1/5] UILD-841: Incorporate jest-axe tests into components for accessibility checks --- package.json | 2 + .../components/Comparison/Comparison.test.tsx | 14 ++ .../ComplexLookupField.test.tsx | 53 +++++ .../ComplexLookupSelectedItem.test.tsx | 14 ++ .../components/HubPreview/HubPreview.test.tsx | 26 +++ .../LookupModal/LookupModal.test.tsx | 19 ++ .../MarcPreview/MarcPreview.test.tsx | 30 +++ .../content/AuthoritiesContent.test.tsx | 27 +++ .../components/content/HubsContent.test.tsx | 15 ++ .../AuthoritiesModal.test.tsx | 54 ++++++ .../modals/HubsModal/HubsModal.test.tsx | 34 ++++ .../modals/SubjectModal/SubjectModal.test.tsx | 24 +++ .../CloseRecord/CloseRecord.test.tsx | 15 +- .../DeleteRecord/DeleteRecord.test.tsx | 19 +- .../DuplicateGroup/DuplicateGroup.test.tsx | 16 +- .../DuplicateGroupContainer.test.tsx | 15 +- .../DuplicateSubcomponentContainer.test.tsx | 17 ++ .../EditControlPane/EditControlPane.test.tsx | 11 ++ .../EditPreview/EditPreview.test.tsx | 15 +- .../EditSection/BlockActions.test.tsx | 40 ++++ .../EditSection/EditSection.test.tsx | 11 ++ .../ProfileSettingsSelector.test.tsx | 16 ++ .../InstancesList/InstancesList.test.tsx | 11 ++ .../RecordControls/RecordControls.test.tsx | 11 ++ .../components/SaveRecord/SaveRecord.test.tsx | 16 +- .../ViewMarcControlPane.test.tsx | 11 ++ .../ModalCloseRecord.test.tsx | 15 +- .../ModalDeleteRecord.test.tsx | 15 +- .../ModalUncontrolledAuthorities.test.tsx | 13 +- .../ModalViewMarc/ModalViewMarc.test.tsx | 18 ++ .../components/modals/Prompt/Prompt.test.tsx | 16 ++ .../components/HubImportControls.test.tsx | 13 +- .../components/HubImportNavPane.test.tsx | 16 +- .../components/HubImportPreview.test.tsx | 17 +- .../DefaultProfileOption.test.tsx | 24 +++ .../DefaultProfileSettingsOption.test.tsx | 25 +++ .../ManageProfileSettingsControlPane.test.tsx | 11 ++ .../ManageProfileSettingsControls.test.tsx | 14 ++ .../ModalCloseProfileSettings.test.tsx | 35 ++++ .../ModalSaveUnusedProfileComponents.test.tsx | 11 ++ .../ProfileSettings/ProfileSettings.test.tsx | 14 ++ .../BaseComponent.test.tsx | 37 ++++ .../ComponentList.test.tsx | 44 +++++ .../ProfileSettingsEditor.test.tsx | 183 +++++++++++++++++- .../ResetComponents.test.tsx | 23 +++ .../ProfileSettingsList.test.tsx | 11 ++ .../ProfilesList/ProfilesList.test.tsx | 17 ++ .../components/Preview/ChildFields.test.tsx | 14 ++ .../components/Preview/Preview.test.tsx | 15 +- .../components/Preview/TitledPreview.test.tsx | 14 ++ .../PreviewActionsDropdown.test.tsx | 15 ++ .../ModalChooseProfile.test.tsx | 57 ++++++ .../WarningMessages.test.tsx | 89 +++++++++ .../ModalWarning.test.tsx | 14 ++ .../ProfileSelectionManager.test.tsx | 158 +++++++++++++++ .../AdvancedSearchModal.test.tsx | 11 ++ .../ui/components/Search/Search.test.tsx | 53 +++++ .../SearchContent/SearchContent.test.tsx | 22 +++ .../SearchContentContainer.test.tsx | 54 ++++++ .../SearchControlPane/ControlPane.test.tsx | 46 +++++ .../SearchControlPane.test.tsx | 85 ++++++++ .../SearchControls/InputsWrapper.test.tsx | 11 ++ .../SearchControls/MetaControls.test.tsx | 21 ++ .../SearchControls/QueryInput.test.tsx | 21 ++ .../SearchControls/ResetButton.test.tsx | 29 +++ .../SearchControls/RootControls.test.tsx | 30 +++ .../SearchControls/SearchBySelect.test.tsx | 24 +++ .../SearchControls/Segment.test.tsx | 37 ++++ .../SearchControls/SegmentContent.test.tsx | 35 ++++ .../SearchControls/SegmentGroup.test.tsx | 41 ++++ .../SearchControls/SourceSelector.test.tsx | 38 ++++ .../SearchControls/SubmitButton.test.tsx | 23 +++ .../SearchEmptyPlaceholder.test.tsx | 16 ++ .../SearchPagination.test.tsx | 25 +++ .../SearchResults/SearchResultsRoot.test.tsx | 22 +++ .../AuthoritiesPageResultList.test.tsx | 11 ++ .../AuthoritiesResultList.test.tsx | 17 ++ .../hubs/HubsLookupResultList.test.tsx | 17 ++ .../results/hubs/HubsResultList.test.tsx | 15 +- .../resources/ResourcesResultList.test.tsx | 22 +++ .../resources/SearchResultEntry.test.tsx | 35 ++++ .../authorities/AssignFormatter.test.tsx | 31 +++ .../AuthorityActionFormatter.test.tsx | 19 ++ .../AuthorityLabelFormatter.test.tsx | 15 ++ .../AuthoritySourceFormatter.test.tsx | 34 ++++ .../authorities/AuthorizedFormatter.test.tsx | 14 ++ .../authorities/TitleFormatter.test.tsx | 23 +++ .../hubs/BaseNotesFormatter.test.tsx | 20 ++ .../hubs/HubActionFormatter.test.tsx | 36 ++++ .../hubs/HubAssignFormatter.test.tsx | 14 ++ .../formatters/hubs/HubLinkFormatter.test.tsx | 19 ++ .../hubs/HubSourceFormatter.test.tsx | 36 ++++ .../hubs/RDANotesFormatter.test.tsx | 22 +++ .../InstanceEditCtlFormatter.test.tsx | 13 ++ .../InstanceSelectCtlFormatter.test.tsx | 21 ++ .../instances/InstanceTitleFormatter.test.tsx | 29 +++ .../__tests__/components/Accordion.test.tsx | 11 ++ src/test/__tests__/components/App.test.tsx | 13 +- .../__tests__/components/DatePicker.test.tsx | 16 ++ .../__tests__/components/DateRange.test.tsx | 14 ++ .../__tests__/components/Dropdown.test.tsx | 34 ++++ .../components/DropdownField.test.tsx | 13 +- .../__tests__/components/Dropzone.test.tsx | 14 +- .../components/ErrorBoundary.test.tsx | 20 ++ src/test/__tests__/components/Input.test.tsx | 14 +- .../components/LiteralField.test.tsx | 13 +- .../__tests__/components/Loading.test.tsx | 11 ++ .../__tests__/components/MarcTooltip.test.tsx | 18 ++ src/test/__tests__/components/Modal.test.tsx | 17 ++ .../__tests__/components/Pagination.test.tsx | 14 ++ .../components/SimpleLookupField.test.tsx | 14 ++ .../components/Table/HeaderCell.test.tsx | 18 ++ .../__tests__/components/Table/Table.test.tsx | 19 +- .../__tests__/components/TableFlex.test.tsx | 15 +- .../__tests__/components/Tooltip.test.tsx | 23 +++ .../views/ExternalResourcePreview.test.tsx | 16 ++ src/test/__tests__/views/Root.test.tsx | 16 +- src/test/__tests__/views/Search.test.tsx | 13 +- src/test/setupTests.ts | 1 + src/views/Edit/Edit.test.tsx | 13 ++ .../PreviewExternalResourceControls.test.tsx | 11 ++ .../PreviewExternalResourcePane.test.tsx | 16 ++ .../ManageProfileSettings.test.tsx | 13 +- .../Root/components/CommonStatus.test.tsx | 21 ++ src/views/Root/components/Footer.test.tsx | 31 +++ .../FullDisplay/FullDisplay.test.tsx | 13 +- .../ModalImport/ModalImport.test.tsx | 17 +- 127 files changed, 3050 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 173cb4e55..316fff4d5 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@testing-library/user-event": "^14.6.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/jest": "^30.0.0", + "@types/jest-axe": "^3.5.9", "@types/lodash": "^4.17.14", "@types/node": "^26.1.1", "@types/react": "^19.2.2", @@ -82,6 +83,7 @@ "husky": "^9.1.7", "identity-obj-proxy": "^3.0.0", "jest": "^30.3.0", + "jest-axe": "^10.0.0", "jest-environment-jsdom": "^30.3.0", "jest-fetch-mock": "^3.0.3", "lint-staged": "^16.4.0", diff --git a/src/features/comparison/components/Comparison/Comparison.test.tsx b/src/features/comparison/components/Comparison/Comparison.test.tsx index cd2d509dc..ff58df323 100644 --- a/src/features/comparison/components/Comparison/Comparison.test.tsx +++ b/src/features/comparison/components/Comparison/Comparison.test.tsx @@ -6,6 +6,7 @@ import { RouterProvider, createMemoryRouter } from 'react-router-dom'; import { Fragment } from 'react/jsx-runtime'; import { fireEvent, render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useComparisonData } from '@/features/comparison/hooks'; @@ -165,4 +166,17 @@ describe('Comparison', () => { expect(getAllByText(1)[0]).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['no comparison items are selected', [] as StoreWithState[], []], + ['only one resource is selected', baseMockState, undefined], + ])('has no accessibility violations when %s', async (_description, stateArgs, comparisonItems) => { + const { container } = renderWithState(stateArgs, comparisonItems); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/complexLookup/components/ComplexLookupField/ComplexLookupField.test.tsx b/src/features/complexLookup/components/ComplexLookupField/ComplexLookupField.test.tsx index 360da7c46..8daa3da4e 100644 --- a/src/features/complexLookup/components/ComplexLookupField/ComplexLookupField.test.tsx +++ b/src/features/complexLookup/components/ComplexLookupField/ComplexLookupField.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ComplexLookupType } from '@/features/complexLookup/constants/complexLookup.constants'; @@ -277,4 +278,56 @@ describe('ComplexLookupField', () => { ); }); }); + + describe('accessibility', () => { + const twoValues = [ + { id: '1', label: 'Item 1', meta: {} }, + { id: '2', label: 'Item 2', meta: {} }, + ]; + + test.each([ + [ + 'read-only input when isNew is false', + { entry: { ...defaultEntry, layout: { ...defaultEntry.layout, isNew: false } } }, + {}, + ], + [ + 'read-only input with formatted value', + { + entry: { ...defaultEntry, layout: { ...defaultEntry.layout, isNew: false } }, + value: twoValues, + }, + { localValue: twoValues }, + ], + ['interactive field with button', { entry: defaultEntry }, {}], + [ + 'selected items when value exists', + { entry: defaultEntry, value: twoValues }, + { localValue: twoValues, buttonLabelId: 'ld.change' }, + ], + ['modal open', { entry: defaultEntry }, { isModalOpen: true }], + [ + 'modal open with initialQuery', + { entry: defaultEntry, value: [{ id: '1', label: 'Test Query', meta: {} }] }, + { localValue: [{ id: '1', label: 'Test Query', meta: {} }], isModalOpen: true }, + ], + ['modalConfig is null', { entry: defaultEntry }, { modalConfig: null, isModalOpen: true }], + [ + 'lookupType is undefined', + { entry: { ...defaultEntry, layout: { ...defaultEntry.layout, api: undefined } } }, + {}, + ], + ])('has no accessibility violations when %s', async (_description, componentProps, hookOverrides) => { + (ComplexLookupHooks.useComplexLookupField as jest.Mock).mockReturnValue({ + ...defaultHookReturn, + ...hookOverrides, + }); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/complexLookup/components/ComplexLookupSelectedItem/ComplexLookupSelectedItem.test.tsx b/src/features/complexLookup/components/ComplexLookupSelectedItem/ComplexLookupSelectedItem.test.tsx index 31cac8b33..4a0b851d5 100644 --- a/src/features/complexLookup/components/ComplexLookupSelectedItem/ComplexLookupSelectedItem.test.tsx +++ b/src/features/complexLookup/components/ComplexLookupSelectedItem/ComplexLookupSelectedItem.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ComplexLookupSelectedItem } from './ComplexLookupSelectedItem'; @@ -56,4 +57,17 @@ describe('ComplexLookupSelectedItem', () => { expect(container).toHaveClass('complex-lookup-selected-withWarning'); expect(container).not.toHaveClass('complex-lookup-selected-embedded'); }); + + describe('accessibility', () => { + test.each([ + ['default props', {}], + ['noWarningValue is false', { noWarningValue: false }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = renderComponent(overrides); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/complexLookup/components/HubPreview/HubPreview.test.tsx b/src/features/complexLookup/components/HubPreview/HubPreview.test.tsx index 8d7a0ab64..2de1f8d46 100644 --- a/src/features/complexLookup/components/HubPreview/HubPreview.test.tsx +++ b/src/features/complexLookup/components/HubPreview/HubPreview.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { SOURCE_TYPES } from '@/common/constants/lookup.constants'; @@ -263,4 +264,29 @@ describe('HubPreview', () => { expect(screen.getByTestId('preview')).toBeInTheDocument(); }); }); + + describe('accessibility', () => { + const emptyPreviewData = { id: 'hub_2', resource: {} as HubResourceData }; + + test.each([ + [ + 'title from meta and assign button', + { previewData: mockPreviewData, previewMeta: mockPreviewMeta, onAssign: mockOnAssign }, + ], + ['meta is null', { previewData: mockPreviewData, previewMeta: null }], + ['onAssign is not provided', { previewData: mockPreviewData, previewMeta: mockPreviewMeta }], + [ + 'preview resource is empty object and onAssign is provided', + { previewData: emptyPreviewData, previewMeta: mockPreviewMeta, onAssign: mockOnAssign }, + ], + ['preview resource is empty object', { previewData: emptyPreviewData, previewMeta: mockPreviewMeta }], + ['preview data is null', { previewData: null, previewMeta: mockPreviewMeta }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/complexLookup/components/LookupModal/LookupModal.test.tsx b/src/features/complexLookup/components/LookupModal/LookupModal.test.tsx index 0eb84695f..c927aa076 100644 --- a/src/features/complexLookup/components/LookupModal/LookupModal.test.tsx +++ b/src/features/complexLookup/components/LookupModal/LookupModal.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { LookupModal } from './LookupModal'; @@ -101,4 +102,22 @@ describe('LookupModal', () => { expect(mockOnClose).toHaveBeenCalledTimes(1); }); }); + + describe('accessibility', () => { + test.each([ + ['open with string title', { isOpen: true, title: 'Test Title' }], + ['closed', { isOpen: false, title: 'Test Title' }], + ['open with ReactElement title', { isOpen: true, title: Custom Title }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render( + +
Test Content
+
, + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/complexLookup/components/MarcPreview/MarcPreview.test.tsx b/src/features/complexLookup/components/MarcPreview/MarcPreview.test.tsx index 4da680ff6..83beb9661 100644 --- a/src/features/complexLookup/components/MarcPreview/MarcPreview.test.tsx +++ b/src/features/complexLookup/components/MarcPreview/MarcPreview.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { IntlProvider } from 'react-intl'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useMarcPreviewStore, useUIStore } from '@/store'; @@ -186,4 +187,33 @@ describe('MarcPreview', () => { expect(assignButton).not.toBeDisabled(); }); }); + + describe('accessibility', () => { + beforeEach(() => { + mockCheckFailedId.mockReturnValue(true); + }); + + test.each([ + ['open with marc data and metadata', true, marcPreviewData, marcPreviewMetadata, {}], + ['isMarcPreviewOpen is false', false, marcPreviewData, marcPreviewMetadata, {}], + ['marcPreviewData is null', true, null, marcPreviewMetadata, {}], + ['assign button is provided', true, marcPreviewData, marcPreviewMetadata, { onAssign: mockOnAssign }], + [ + 'assign button is disabled via checkFailedId', + true, + marcPreviewData, + marcPreviewMetadata, + { onAssign: mockOnAssign, checkFailedId: mockCheckFailedId }, + ], + ])( + 'has no accessibility violations when %s', + async (_description, isMarcPreviewOpen, marcData, metadata, props) => { + const { container } = renderComponent(isMarcPreviewOpen, marcData, metadata, props); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }, + ); + }); }); diff --git a/src/features/complexLookup/components/content/AuthoritiesContent.test.tsx b/src/features/complexLookup/components/content/AuthoritiesContent.test.tsx index f4a90b1a3..2d0343334 100644 --- a/src/features/complexLookup/components/content/AuthoritiesContent.test.tsx +++ b/src/features/complexLookup/components/content/AuthoritiesContent.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { AuthoritiesContent } from './AuthoritiesContent'; @@ -310,4 +311,30 @@ describe('AuthoritiesContent', () => { expect(screen.queryByTestId('content-container')).not.toBeInTheDocument(); }); }); + + describe('accessibility', () => { + const baseProps = { + isMarcPreviewOpen: false, + isMarcLoading: false, + handleAuthoritiesAssign: mockHandleAuthoritiesAssign, + handleTitleClick: mockHandleTitleClick, + handleCloseMarcPreview: mockHandleCloseMarcPreview, + }; + + test.each([ + ['result list view', {}], + ['result list view with checkFailedId', { checkFailedId: mockCheckFailedId }], + ['result list view with notSpecifiedLabel', { notSpecifiedLabel: 'Localized fallback' }], + ['MARC preview is loading', { isMarcPreviewOpen: true, isMarcLoading: true }], + ['MARC preview is open', { isMarcPreviewOpen: true }], + ['MARC preview is open without complex flow', { isMarcPreviewOpen: true, hasComplexFlow: false }], + ['MARC preview is open with checkFailedId', { isMarcPreviewOpen: true, checkFailedId: mockCheckFailedId }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/complexLookup/components/content/HubsContent.test.tsx b/src/features/complexLookup/components/content/HubsContent.test.tsx index aedbe90e5..2a9a7dd2f 100644 --- a/src/features/complexLookup/components/content/HubsContent.test.tsx +++ b/src/features/complexLookup/components/content/HubsContent.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { HubsContent } from './HubsContent'; @@ -102,4 +103,18 @@ describe('HubsContent', () => { expect(screen.getByTestId('hubs-lookup-result-list')).toBeInTheDocument(); }); }); + + describe('accessibility', () => { + test.each([ + ['default', {}], + ['handleHubTitleClick provided', { handleHubTitleClick: mockHandleHubTitleClick }], + ['isAssigning is true', { isAssigning: true }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/complexLookup/components/modals/AuthoritiesModal/AuthoritiesModal.test.tsx b/src/features/complexLookup/components/modals/AuthoritiesModal/AuthoritiesModal.test.tsx index 9721e437c..2c31cbcb5 100644 --- a/src/features/complexLookup/components/modals/AuthoritiesModal/AuthoritiesModal.test.tsx +++ b/src/features/complexLookup/components/modals/AuthoritiesModal/AuthoritiesModal.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useSchemaPipeline } from '@/common/hooks/useSchemaPipeline'; @@ -471,4 +472,57 @@ describe('AuthoritiesModal', () => { }); }); }); + + describe('accessibility', () => { + const baseAuthoritiesModalLogicReturn = { + isMarcPreviewOpen: false, + isMarcLoading: false, + authoritiesData: { + onSegmentEnter: mockOnSegmentEnter, + }, + handleTitleClick: mockHandleTitleClick, + handleAuthoritiesAssign: mockHandleAuthoritiesAssign, + handleCloseMarcPreview: mockHandleCloseMarcPreview, + handleResetMarcPreview: jest.fn(), + checkFailedId: undefined, + cleanup: { + setIsMarcPreviewOpen: mockSetIsMarcPreviewOpen, + resetPreview: mockResetPreview, + resetMarcPreviewData: mockResetComplexValue, + resetMarcPreviewMetadata: mockResetMetadata, + }, + }; + + test.each([ + ['modal open with search results', {}, {}], + ['modal closed', { isOpen: false }, {}], + ['MARC preview is open', {}, { isMarcPreviewOpen: true, isMarcLoading: false }], + ['MARC preview is loading', {}, { isMarcPreviewOpen: true, isMarcLoading: true }], + [ + 'modal config with notSpecified label', + { + modalConfig: { + labels: { + button: { base: 'ld.assignAuthority', change: 'ld.change' }, + notSpecified: 'ld.notSpecified', + }, + } as unknown as ModalConfig, + }, + {}, + ], + ])('has no accessibility violations when %s', async (_description, componentProps, hookOverrides) => { + (ComplexLookupHooks.useAuthoritiesModalLogic as jest.Mock).mockReturnValue({ + ...baseAuthoritiesModalLogicReturn, + ...hookOverrides, + }); + + const { container } = render( + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/complexLookup/components/modals/HubsModal/HubsModal.test.tsx b/src/features/complexLookup/components/modals/HubsModal/HubsModal.test.tsx index 05d3c1263..2597734dd 100644 --- a/src/features/complexLookup/components/modals/HubsModal/HubsModal.test.tsx +++ b/src/features/complexLookup/components/modals/HubsModal/HubsModal.test.tsx @@ -1,5 +1,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as ComplexLookupHooks from '@/features/complexLookup/hooks'; @@ -281,4 +282,37 @@ describe('HubsModal', () => { }); }); }); + + describe('accessibility', () => { + const baseHubPreviewProps = { + handleHubAssign: mockHandleHubAssign, + isAssigning: false, + isHubPreviewOpen: false, + isPreviewLoading: false, + previewData: null, + previewMeta: null, + handleHubTitleClick: jest.fn(), + handleCloseHubPreview: jest.fn(), + handleHubPreviewAssign: jest.fn(), + }; + + test.each([ + ['modal open with search results', {}, {}], + ['modal closed', { isOpen: false }, {}], + ['isAssigning is true', {}, { isAssigning: true }], + ])('has no accessibility violations when %s', async (_description, componentProps, hubPreviewOverrides) => { + (ComplexLookupHooks.useModalWithHubPreview as jest.Mock).mockReturnValue({ + hubPreviewProps: { ...baseHubPreviewProps, ...hubPreviewOverrides }, + handleModalClose: mockHandleModalClose, + }); + + const { container } = renderWithProviders( + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/complexLookup/components/modals/SubjectModal/SubjectModal.test.tsx b/src/features/complexLookup/components/modals/SubjectModal/SubjectModal.test.tsx index 979cbd75e..87ed91ce0 100644 --- a/src/features/complexLookup/components/modals/SubjectModal/SubjectModal.test.tsx +++ b/src/features/complexLookup/components/modals/SubjectModal/SubjectModal.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ModalConfig } from '@/features/complexLookup/configs/modalRegistry'; import * as ComplexLookupHooks from '@/features/complexLookup/hooks'; @@ -373,4 +374,27 @@ describe('SubjectModal', () => { expect(mockHandleCloseHubPreview).toHaveBeenCalledTimes(1); }); }); + + describe('accessibility', () => { + test.each([ + ['modal open with search results', {}], + ['modal closed', { isOpen: false }], + [ + 'complex flow with entry and context provided', + { + entry: { uuid: 'entry_1' } as SchemaEntry, + lookupContext: 'test-context', + modalConfig: { type: 'authorities' } as unknown as ModalConfig, + }, + ], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render( + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/CloseRecord/CloseRecord.test.tsx b/src/features/edit/components/CloseRecord/CloseRecord.test.tsx index 2a2406433..6c81dcf8a 100644 --- a/src/features/edit/components/CloseRecord/CloseRecord.test.tsx +++ b/src/features/edit/components/CloseRecord/CloseRecord.test.tsx @@ -2,11 +2,16 @@ import '@/test/__mocks__/components/Modal.mock'; import { discardRecord } from '@/test/__mocks__/features/resources/hooks/useRecordNavigation.mock'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { CloseRecord } from '.'; describe('CloseRecord', () => { - beforeEach(() => render()); + let container: HTMLElement; + + beforeEach(() => { + ({ container } = render()); + }); test('renders "Close record" button', () => { expect(screen.getByTestId('close-record-button')).toBeInTheDocument(); @@ -17,4 +22,12 @@ describe('CloseRecord', () => { expect(discardRecord).toHaveBeenCalledTimes(1); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/DeleteRecord/DeleteRecord.test.tsx b/src/features/edit/components/DeleteRecord/DeleteRecord.test.tsx index 605d29596..a55191c9f 100644 --- a/src/features/edit/components/DeleteRecord/DeleteRecord.test.tsx +++ b/src/features/edit/components/DeleteRecord/DeleteRecord.test.tsx @@ -6,6 +6,7 @@ import '@/test/__mocks__/features/resources/hooks/useRecordMutations.mock'; import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useInputsStore } from '@/store'; @@ -43,7 +44,7 @@ describe('DeleteRecord', () => { }); test('renders disabled "Delete record" button for Edit resource page', () => { - checkButtonDisabledState.mockReturnValue('/test_url'); + checkButtonDisabledState.mockReturnValue(true); renderComponent(mockedRecord); @@ -66,4 +67,20 @@ describe('DeleteRecord', () => { expect(openModal).toHaveBeenCalledTimes(1); }); + + describe('accessibility', () => { + test.each([ + ['not disabled', undefined, mockedRecord], + ['disabled for Edit resource page', true, mockedRecord], + ['disabled for Create resource page', undefined, null], + ])('has no accessibility violations when %s', async (_description, disabledStateReturn, recordState) => { + checkButtonDisabledState.mockReturnValue(disabledStateReturn); + + const { container } = renderComponent(recordState); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/DuplicateGroup/DuplicateGroup.test.tsx b/src/features/edit/components/DuplicateGroup/DuplicateGroup.test.tsx index 1a99fc557..67b817e1f 100644 --- a/src/features/edit/components/DuplicateGroup/DuplicateGroup.test.tsx +++ b/src/features/edit/components/DuplicateGroup/DuplicateGroup.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { DuplicateGroup } from './DuplicateGroup'; @@ -7,7 +8,7 @@ describe('DuplicateGroup', () => { const onClick = jest.fn(); function renderComponent(hasDeleteButton = true) { - render(); + return render(); } test('renders DuplicateGroup component', () => { @@ -30,4 +31,17 @@ describe('DuplicateGroup', () => { expect(onClick).toHaveBeenCalledTimes(1); }); + + describe('accessibility', () => { + test.each([ + ['default', true], + ['without "Delete" button', false], + ])('has no accessibility violations when %s', async (_description, hasDeleteButton) => { + const { container } = renderComponent(hasDeleteButton); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/DuplicateGroupContainer/DuplicateGroupContainer.test.tsx b/src/features/edit/components/DuplicateGroupContainer/DuplicateGroupContainer.test.tsx index fa2b2341b..e2a00aabe 100644 --- a/src/features/edit/components/DuplicateGroupContainer/DuplicateGroupContainer.test.tsx +++ b/src/features/edit/components/DuplicateGroupContainer/DuplicateGroupContainer.test.tsx @@ -1,6 +1,7 @@ import { Fragment, ReactNode } from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { DuplicateGroupContainer } from './DuplicateGroupContainer'; @@ -26,14 +27,16 @@ describe('DuplicateGroupContainer', () => { const { getByText, getByTestId, queryByText } = screen; const toggleButton = () => fireEvent.click(getByTestId('expand-collapse-button')); + let container: HTMLElement; + beforeEach(() => { - render( + ({ container } = render(
{uuid}
} twins={[mockClonedByUuid]} />, - ); + )); }); test('toggles collapsible component and shows the number of entries', () => { @@ -45,4 +48,12 @@ describe('DuplicateGroupContainer', () => { expect(getByText(mockClonedByUuid)).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/DuplicateSubcomponentContainer/DuplicateSubcomponentContainer.test.tsx b/src/features/edit/components/DuplicateSubcomponentContainer/DuplicateSubcomponentContainer.test.tsx index 248a35bcb..69ba00f18 100644 --- a/src/features/edit/components/DuplicateSubcomponentContainer/DuplicateSubcomponentContainer.test.tsx +++ b/src/features/edit/components/DuplicateSubcomponentContainer/DuplicateSubcomponentContainer.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { IFields } from '../Fields'; import { DuplicateSubcomponentContainer } from './DuplicateSubcomponentContainer'; @@ -28,4 +29,20 @@ describe('DuplicateSubcomponentContainer', () => { expect(getByTestId('test-repeatable-subcomponent-clonedByUuid_1')).toBeInTheDocument(); expect(getByTestId('test-repeatable-subcomponent-clonedByUuid_2')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = render( + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/EditControlPane/EditControlPane.test.tsx b/src/features/edit/components/EditControlPane/EditControlPane.test.tsx index 411146aca..80a200fed 100644 --- a/src/features/edit/components/EditControlPane/EditControlPane.test.tsx +++ b/src/features/edit/components/EditControlPane/EditControlPane.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { RouterProvider, createMemoryRouter } from 'react-router'; import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { PROFILE_BFIDS } from '@/common/constants/bibframe.constants'; import { ROUTES } from '@/common/constants/routes.constants'; @@ -50,4 +51,14 @@ describe('EditControlPane', () => { expect(container.querySelector('.heading')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderWrapper(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/EditPreview/EditPreview.test.tsx b/src/features/edit/components/EditPreview/EditPreview.test.tsx index b32f3200e..b0bfe04f9 100644 --- a/src/features/edit/components/EditPreview/EditPreview.test.tsx +++ b/src/features/edit/components/EditPreview/EditPreview.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { RouterProvider, createMemoryRouter } from 'react-router-dom'; import { act, fireEvent, render, screen, within } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { PROFILE_BFIDS } from '@/common/constants/bibframe.constants'; import { ResourceType } from '@/common/constants/record.constants'; @@ -42,6 +43,8 @@ jest.mock('react-router-dom', () => ({ })); describe('EditPreview', () => { + let container: HTMLElement; + afterEach(() => { jest.restoreAllMocks(); }); @@ -69,13 +72,13 @@ describe('EditPreview', () => { }, ]); - render( + ({ container } = render( }], { initialEntries: ['/resources/create?type=work'], })} />, - ); + )); }); const { getByTestId } = screen; @@ -118,4 +121,12 @@ describe('EditPreview', () => { // After navigation, selection is reset -> instances list shown again expect(view.getByTestId('instances-list')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/EditSection/BlockActions.test.tsx b/src/features/edit/components/EditSection/BlockActions.test.tsx index 3eeef37c1..78372089c 100644 --- a/src/features/edit/components/EditSection/BlockActions.test.tsx +++ b/src/features/edit/components/EditSection/BlockActions.test.tsx @@ -5,6 +5,7 @@ import * as Router from 'react-router-dom'; import { RouterProvider, createMemoryRouter } from 'react-router-dom'; import { fireEvent, render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { PROFILE_BFIDS } from '@/common/constants/bibframe.constants'; import { BFLITE_URIS } from '@/common/constants/bibframeMapping.constants'; @@ -186,4 +187,43 @@ describe('BlockActions', () => { const button = await findByText('ld.inventoryView'); expect(button.closest('button')).toBeDisabled(); }); + + describe('accessibility', () => { + test.each([ + ['Instance entry in edit mode', () => renderBlockActions(instanceEntry)], + [ + 'create mode', + () => { + setInitialGlobalState([ + { + store: useInputsState, + state: { selectedRecordBlocks: { block: 'test-block' } }, + }, + ]); + + jest.spyOn(Router, 'useParams').mockReturnValue({ resourceId: 'resource_1' }); + + return render( + , + }, + ], + { initialEntries: [ROUTES.RESOURCE_CREATE.uri] }, + )} + />, + ); + }, + ], + ])('has no accessibility violations for %s', async (_description, renderFn) => { + const { container } = renderFn(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/EditSection/EditSection.test.tsx b/src/features/edit/components/EditSection/EditSection.test.tsx index 885c1a9da..e8c21e26a 100644 --- a/src/features/edit/components/EditSection/EditSection.test.tsx +++ b/src/features/edit/components/EditSection/EditSection.test.tsx @@ -8,6 +8,7 @@ import { RouterProvider, createMemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, waitFor, within } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { routes } from '@/App'; import { AdvancedFieldType } from '@/common/constants/uiControls.constants'; @@ -459,4 +460,14 @@ describe('EditSection', () => { expect(within(section).getByPlaceholderText('ld.placeholder.processing')).toBeInTheDocument(); }); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderScreen(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/EditSection/ProfileSettingsSelector.test.tsx b/src/features/edit/components/EditSection/ProfileSettingsSelector.test.tsx index ee2e5b1bb..4d6928fb1 100644 --- a/src/features/edit/components/EditSection/ProfileSettingsSelector.test.tsx +++ b/src/features/edit/components/EditSection/ProfileSettingsSelector.test.tsx @@ -4,6 +4,7 @@ import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useProfileStore } from '@/store'; @@ -128,4 +129,19 @@ describe('ProfileSettingsSelector', () => { expect(mockSetSelectedProfileSettingsId).toHaveBeenCalledWith('15'); }); }); + + describe('accessibility', () => { + test.each([ + ['with options', true], + ['without options', false], + ])('has no accessibility violations when %s', async (_description, withOptions) => { + mockGetRecordProfileId.mockReturnValue(mockProfileId); + + const { container } = renderComponent(withOptions); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/InstancesList/InstancesList.test.tsx b/src/features/edit/components/InstancesList/InstancesList.test.tsx index e34370623..b8fe781d5 100644 --- a/src/features/edit/components/InstancesList/InstancesList.test.tsx +++ b/src/features/edit/components/InstancesList/InstancesList.test.tsx @@ -4,6 +4,7 @@ import { navigateToEditPage } from '@/test/__mocks__/common/hooks/useNavigateToE import { BrowserRouter } from 'react-router-dom'; import { fireEvent, render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as RecordFormatter from '@/common/helpers/recordFormatting.helper'; @@ -82,4 +83,14 @@ describe('InstancesList', () => { expect(navigateToEditPage).toHaveBeenCalled(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderWithProps(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/RecordControls/RecordControls.test.tsx b/src/features/edit/components/RecordControls/RecordControls.test.tsx index b87820428..50db1246e 100644 --- a/src/features/edit/components/RecordControls/RecordControls.test.tsx +++ b/src/features/edit/components/RecordControls/RecordControls.test.tsx @@ -1,4 +1,5 @@ import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { RecordControls } from './RecordControls'; @@ -17,4 +18,14 @@ describe('RecordControls', () => { expect(getByTestId('save-record-component')).toBeInTheDocument(); expect(getByTestId('close-record-component')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/SaveRecord/SaveRecord.test.tsx b/src/features/edit/components/SaveRecord/SaveRecord.test.tsx index b7434875a..c55d93849 100644 --- a/src/features/edit/components/SaveRecord/SaveRecord.test.tsx +++ b/src/features/edit/components/SaveRecord/SaveRecord.test.tsx @@ -4,6 +4,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { BrowserRouter } from 'react-router-dom'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useStatusStore } from '@/store'; @@ -18,7 +19,7 @@ describe('SaveRecord', () => { }, ]); - render( + return render( , @@ -45,4 +46,17 @@ describe('SaveRecord', () => { expect(saveRecord).toHaveBeenCalledTimes(1); }); + + describe('accessibility', () => { + test.each([ + ['enabled', true], + ['disabled', false], + ])('has no accessibility violations when %s', async (_description, isRecordEdited) => { + const { container } = renderSaveRecordComponent(isRecordEdited); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/ViewMarcControlPane/ViewMarcControlPane.test.tsx b/src/features/edit/components/ViewMarcControlPane/ViewMarcControlPane.test.tsx index 7ce0a3304..0a5a1b741 100644 --- a/src/features/edit/components/ViewMarcControlPane/ViewMarcControlPane.test.tsx +++ b/src/features/edit/components/ViewMarcControlPane/ViewMarcControlPane.test.tsx @@ -1,4 +1,5 @@ import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ViewMarcControlPane } from './ViewMarcControlPane'; @@ -8,4 +9,14 @@ describe('ViewMarcControlPane', () => { expect(getByTestId('view-marc-control-pane')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/modals/ModalCloseRecord/ModalCloseRecord.test.tsx b/src/features/edit/components/modals/ModalCloseRecord/ModalCloseRecord.test.tsx index 761504f37..0a81f50ac 100644 --- a/src/features/edit/components/modals/ModalCloseRecord/ModalCloseRecord.test.tsx +++ b/src/features/edit/components/modals/ModalCloseRecord/ModalCloseRecord.test.tsx @@ -1,6 +1,7 @@ import { createModalContainer } from '@/test/__mocks__/common/misc/createModalContainer.mock'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ModalCloseRecord } from './ModalCloseRecord'; @@ -9,12 +10,16 @@ describe('ModalCloseRecord', () => { const onSubmit = jest.fn(); const onClose = jest.fn(); + let container: HTMLElement; + beforeAll(() => { createModalContainer(); }); beforeEach(() => { - render(); + ({ container } = render( + , + )); }); test('renders modal component', () => { @@ -34,4 +39,12 @@ describe('ModalCloseRecord', () => { expect(onCancel).toHaveBeenCalledTimes(1); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/modals/ModalDeleteRecord/ModalDeleteRecord.test.tsx b/src/features/edit/components/modals/ModalDeleteRecord/ModalDeleteRecord.test.tsx index 80edcd23a..004817576 100644 --- a/src/features/edit/components/modals/ModalDeleteRecord/ModalDeleteRecord.test.tsx +++ b/src/features/edit/components/modals/ModalDeleteRecord/ModalDeleteRecord.test.tsx @@ -1,6 +1,7 @@ import { createModalContainer } from '@/test/__mocks__/common/misc/createModalContainer.mock'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ModalDeleteRecord } from './ModalDeleteRecord'; @@ -8,12 +9,16 @@ describe('ModalDeleteRecord', () => { const toggleIsOpen = jest.fn(); const deleteRecord = jest.fn(); + let container: HTMLElement; + beforeAll(() => { createModalContainer(); }); beforeEach(() => { - render(); + ({ container } = render( + , + )); }); test('renders modal component', () => { @@ -31,4 +36,12 @@ describe('ModalDeleteRecord', () => { expect(toggleIsOpen).toHaveBeenCalledWith(false); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/modals/ModalUncontrolledAuthorities/ModalUncontrolledAuthorities.test.tsx b/src/features/edit/components/modals/ModalUncontrolledAuthorities/ModalUncontrolledAuthorities.test.tsx index c80815f04..a503db3e0 100644 --- a/src/features/edit/components/modals/ModalUncontrolledAuthorities/ModalUncontrolledAuthorities.test.tsx +++ b/src/features/edit/components/modals/ModalUncontrolledAuthorities/ModalUncontrolledAuthorities.test.tsx @@ -1,6 +1,7 @@ import { createModalContainer } from '@/test/__mocks__/common/misc/createModalContainer.mock'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ModalUncontrolledAuthorities } from './ModalUncontrolledAuthorities'; @@ -12,12 +13,14 @@ describe('ModalUncontrolledAuthorities', () => { onClose: jest.fn(), }; + let container: HTMLElement; + beforeAll(() => { createModalContainer(); }); beforeEach(() => { - render(); + ({ container } = render()); }); test('renders modal component with warning message', () => { @@ -50,4 +53,12 @@ describe('ModalUncontrolledAuthorities', () => { expect(props.onClose).toHaveBeenCalledTimes(1); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/modals/ModalViewMarc/ModalViewMarc.test.tsx b/src/features/edit/components/modals/ModalViewMarc/ModalViewMarc.test.tsx index 7e2a0314c..c01c0ce0d 100644 --- a/src/features/edit/components/modals/ModalViewMarc/ModalViewMarc.test.tsx +++ b/src/features/edit/components/modals/ModalViewMarc/ModalViewMarc.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useMarcPreviewStore } from '@/store'; @@ -50,4 +51,21 @@ describe('ViewMarcModal', () => { expect(await findByText(leader, { exact: false })).toBeInTheDocument(); expect(await findByText(subfieldContent, { exact: false })).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + setInitialGlobalState([ + { + store: useMarcPreviewStore, + state: { basicValue: mockMarcPreview }, + }, + ]); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/edit/components/modals/Prompt/Prompt.test.tsx b/src/features/edit/components/modals/Prompt/Prompt.test.tsx index bbb96cd31..812029aa6 100644 --- a/src/features/edit/components/modals/Prompt/Prompt.test.tsx +++ b/src/features/edit/components/modals/Prompt/Prompt.test.tsx @@ -5,6 +5,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { RouterProvider, createMemoryRouter } from 'react-router-dom'; import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useConfigStore, useLoadingStateStore } from '@/store'; @@ -119,4 +120,19 @@ describe('Prompt', () => { expect(mockResetIsLoading).not.toHaveBeenCalled(); }); + + describe('accessibility', () => { + test.each([ + ['navigation is blocked', true], + ['navigation is not blocked', false], + ])('has no accessibility violations when %s', async (_description, isBlocking) => { + cleanup(); + + const { container } = renderPrompt(isBlocking); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/hubImport/components/HubImportControls.test.tsx b/src/features/hubImport/components/HubImportControls.test.tsx index aa7a92f31..10924fd9e 100644 --- a/src/features/hubImport/components/HubImportControls.test.tsx +++ b/src/features/hubImport/components/HubImportControls.test.tsx @@ -1,6 +1,7 @@ import { BrowserRouter } from 'react-router-dom'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { HubImportControls } from './HubImportControls'; @@ -35,7 +36,7 @@ describe('HubImportControls', () => { }); const renderComponent = () => { - render( + return render( , @@ -74,4 +75,14 @@ describe('HubImportControls', () => { expect(mockImportHubForEdit).not.toHaveBeenCalled(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderComponent(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/hubImport/components/HubImportNavPane.test.tsx b/src/features/hubImport/components/HubImportNavPane.test.tsx index ddd7bf2cc..18481fee9 100644 --- a/src/features/hubImport/components/HubImportNavPane.test.tsx +++ b/src/features/hubImport/components/HubImportNavPane.test.tsx @@ -1,6 +1,7 @@ import { BrowserRouter } from 'react-router-dom'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { TYPE_URIS } from '@/common/constants/bibframe.constants'; @@ -47,7 +48,7 @@ describe('HubImportNavPane', () => { const renderComponent = (hubData?: RecordEntry) => { mockUseHubQuery.mockReturnValue({ data: hubData ?? null }); - render( + return render( , @@ -86,4 +87,17 @@ describe('HubImportNavPane', () => { const closeButton = screen.getByTestId('nav-close-button'); expect(closeButton).toHaveAttribute('aria-label'); }); + + describe('accessibility', () => { + test.each([ + ['with record', mockRecord], + ['without record', undefined], + ])('has no accessibility violations when %s', async (_description, hubData) => { + const { container } = renderComponent(hubData); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/hubImport/components/HubImportPreview.test.tsx b/src/features/hubImport/components/HubImportPreview.test.tsx index 86ecf35f5..a4cc5cc86 100644 --- a/src/features/hubImport/components/HubImportPreview.test.tsx +++ b/src/features/hubImport/components/HubImportPreview.test.tsx @@ -4,6 +4,7 @@ import { BrowserRouter } from 'react-router-dom'; import * as routerDom from 'react-router-dom'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { TYPE_URIS } from '@/common/constants/bibframe.constants'; @@ -67,7 +68,7 @@ describe('HubImportPreview', () => { error: null, }); - render( + return render( , @@ -121,4 +122,18 @@ describe('HubImportPreview', () => { expect(screen.getByTestId('loading-component')).toBeInTheDocument(); expect(screen.queryByTestId('preview-component')).not.toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each<[string, RecordEntry | null | undefined, boolean]>([ + ['loading with no record', null, true], + ['loaded with record', mockRecord, false], + ['loading with record', mockRecord, true], + ])('has no accessibility violations when %s', async (_description, record, isLoading) => { + const { container } = renderComponent(record, isLoading); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/DefaultProfileOption/DefaultProfileOption.test.tsx b/src/features/manageProfileSettings/components/DefaultProfileOption/DefaultProfileOption.test.tsx index f34ced535..967f26afa 100644 --- a/src/features/manageProfileSettings/components/DefaultProfileOption/DefaultProfileOption.test.tsx +++ b/src/features/manageProfileSettings/components/DefaultProfileOption/DefaultProfileOption.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { MemoryRouter } from 'react-router-dom'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { fetchPreferredProfiles } from '@/common/api/profiles.api'; import { BFLITE_URIS } from '@/common/constants/bibframeMapping.constants'; @@ -128,4 +129,27 @@ describe('DefaultProfileOption', () => { expect(screen.getByTestId('type-default-setting')).toBeChecked(); }); }); + + describe('accessibility', () => { + test.each([ + ['not cached, string id profile', false, mockProfile, mockPreferredProfiles], + ['cached, string id profile', true, mockProfile, undefined], + ['cached, number id profile', true, numberIdProfile, undefined], + ['not cached, number id profile', false, numberIdProfile, [alphaIdProfile]], + ] as const)('has no accessibility violations when %s', async (_description, cached, selected, mockReturn) => { + if (mockReturn) { + (fetchPreferredProfiles as jest.Mock).mockReturnValue(mockReturn); + } + + const { container } = renderComponent(cached, selected); + + await waitFor(() => { + expect(screen.getByTestId('type-default-setting')).toBeInTheDocument(); + }); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/DefaultProfileSettingsOption/DefaultProfileSettingsOption.test.tsx b/src/features/manageProfileSettings/components/DefaultProfileSettingsOption/DefaultProfileSettingsOption.test.tsx index 58914547a..acab51584 100644 --- a/src/features/manageProfileSettings/components/DefaultProfileSettingsOption/DefaultProfileSettingsOption.test.tsx +++ b/src/features/manageProfileSettings/components/DefaultProfileSettingsOption/DefaultProfileSettingsOption.test.tsx @@ -5,6 +5,7 @@ import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { fetchPreferredProfileSettings } from '@/common/api/profiles.api'; import { PROFILE_SETTINGS_DEFAULT_OPTION } from '@/common/constants/profileSettings.constants'; @@ -134,4 +135,28 @@ describe('DefaultProfileSettingsOption', () => { expect(screen.getByTestId('default-profile-settings-control')).not.toBeChecked(); }); }); + + describe('accessibility', () => { + test.each([ + ['settings match preferred settings', mockProfileId, mockProfileSettingsId, mockPreferredProfileSettings], + ['profile is undefined', null, PROFILE_SETTINGS_DEFAULT_OPTION, mockPreferredProfileSettings], + ['selected settings not preferred', mockProfileId, mockProfileSettingsId, mockAlternatePreferredProfileSettings], + ['no preferred settings', mockProfileId, mockProfileSettingsId, []], + ] as const)( + 'has no accessibility violations when %s', + async (_description, profileId, profileSettingsId, mockReturn) => { + (fetchPreferredProfileSettings as jest.Mock).mockReturnValue(mockReturn); + + const { container } = renderComponent(profileId, profileSettingsId); + + await waitFor(() => { + expect(screen.getByTestId('default-profile-settings-control')).toBeInTheDocument(); + }); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }, + ); + }); }); diff --git a/src/features/manageProfileSettings/components/ManageProfileSettingsControlPane/ManageProfileSettingsControlPane.test.tsx b/src/features/manageProfileSettings/components/ManageProfileSettingsControlPane/ManageProfileSettingsControlPane.test.tsx index 18c3a82a8..b4dfefbed 100644 --- a/src/features/manageProfileSettings/components/ManageProfileSettingsControlPane/ManageProfileSettingsControlPane.test.tsx +++ b/src/features/manageProfileSettings/components/ManageProfileSettingsControlPane/ManageProfileSettingsControlPane.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { MemoryRouter } from 'react-router-dom'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useManageProfileSettingsState, useUIState } from '@/store'; @@ -54,4 +55,14 @@ describe('ManageProfileSettingsControlPane', () => { expect(setIsManageProfileSettingsUnsavedModalOpen).not.toHaveBeenCalled(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderComponent(false); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ManageProfileSettingsControls/ManageProfileSettingsControls.test.tsx b/src/features/manageProfileSettings/components/ManageProfileSettingsControls/ManageProfileSettingsControls.test.tsx index 36f18054d..1e8d4cd94 100644 --- a/src/features/manageProfileSettings/components/ManageProfileSettingsControls/ManageProfileSettingsControls.test.tsx +++ b/src/features/manageProfileSettings/components/ManageProfileSettingsControls/ManageProfileSettingsControls.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { MemoryRouter } from 'react-router-dom'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useManageProfileSettingsState, useUIState } from '@/store'; @@ -124,4 +125,17 @@ describe('ManageProfileSettingsControls', () => { expect(mockNavigate).toHaveBeenCalled(); }); }); + + describe('accessibility', () => { + test.each([ + ['no modifications, no unused components', false, false], + ['modifications exist', true, false], + ])('has no accessibility violations when %s', async (_description, isModified, hasUnused) => { + const { container } = renderComponent(isModified, hasUnused); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ModalCloseProfileSettings/ModalCloseProfileSettings.test.tsx b/src/features/manageProfileSettings/components/ModalCloseProfileSettings/ModalCloseProfileSettings.test.tsx index 47ff5b0d6..13dd12f61 100644 --- a/src/features/manageProfileSettings/components/ModalCloseProfileSettings/ModalCloseProfileSettings.test.tsx +++ b/src/features/manageProfileSettings/components/ModalCloseProfileSettings/ModalCloseProfileSettings.test.tsx @@ -5,6 +5,7 @@ import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ProfileSettingsMode } from '@/common/constants/profileSettings.constants'; @@ -272,4 +273,38 @@ describe('ModalCloseProfileSettings', () => { expect(mockSetSettingsName).toHaveBeenCalledWith('edit-name'); }); }); + + describe('accessibility', () => { + test.each([ + ['default state', () => {}], + [ + 'not in landing mode', + () => + setInitialGlobalState([ + { + store: useManageProfileSettingsState, + state: { mode: ProfileSettingsMode.Editing }, + }, + ]), + ], + [ + 'in landing mode', + () => + setInitialGlobalState([ + { + store: useManageProfileSettingsState, + state: { mode: ProfileSettingsMode.Landing }, + }, + ]), + ], + ])('has no accessibility violations when %s', async (_description, setup) => { + setup(); + + const { container } = renderComponent(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ModalSaveUnusedProfileComponents/ModalSaveUnusedProfileComponents.test.tsx b/src/features/manageProfileSettings/components/ModalSaveUnusedProfileComponents/ModalSaveUnusedProfileComponents.test.tsx index 999602872..d4a3cd56f 100644 --- a/src/features/manageProfileSettings/components/ModalSaveUnusedProfileComponents/ModalSaveUnusedProfileComponents.test.tsx +++ b/src/features/manageProfileSettings/components/ModalSaveUnusedProfileComponents/ModalSaveUnusedProfileComponents.test.tsx @@ -4,6 +4,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { MemoryRouter } from 'react-router-dom'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useManageProfileSettingsState } from '@/store'; @@ -109,4 +110,14 @@ describe('ModalSaveUnusedProfileComponents', () => { expect(mockSetIsClosingNext).toHaveBeenCalledWith(false); }); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderComponent(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ProfileSettings/ProfileSettings.test.tsx b/src/features/manageProfileSettings/components/ProfileSettings/ProfileSettings.test.tsx index 36772f41c..0b3966219 100644 --- a/src/features/manageProfileSettings/components/ProfileSettings/ProfileSettings.test.tsx +++ b/src/features/manageProfileSettings/components/ProfileSettings/ProfileSettings.test.tsx @@ -4,6 +4,7 @@ import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { act, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { fetchProfile } from '@/common/api/profiles.api'; import { StatusType } from '@/common/constants/status.constants'; @@ -347,4 +348,17 @@ describe('ProfileSettings', () => { expect(useManageProfileSettingsState.getState().fullProfile).toEqual(profileTwo); }); }); + + describe('accessibility', () => { + test.each([ + ['selected profile missing', false, false], + ['profile selected', true, false], + ])('has no accessibility violations when %s', async (_description, selected, defaultMeta) => { + const { container } = renderComponent(selected, defaultMeta); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ProfileSettingsEditor/BaseComponent.test.tsx b/src/features/manageProfileSettings/components/ProfileSettingsEditor/BaseComponent.test.tsx index 3e6d27776..1675c67f4 100644 --- a/src/features/manageProfileSettings/components/ProfileSettingsEditor/BaseComponent.test.tsx +++ b/src/features/manageProfileSettings/components/ProfileSettingsEditor/BaseComponent.test.tsx @@ -4,6 +4,7 @@ import { MemoryRouter } from 'react-router-dom'; import { DndContext } from '@dnd-kit/core'; import { SortableContext } from '@dnd-kit/sortable'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { DraggingComponent } from './DraggingComponent'; import { SelectedComponent } from './SelectedComponent'; @@ -193,4 +194,40 @@ describe('BaseComponent', () => { expect(screen.getByTestId('move-menu')).toHaveFocus(); }); }); + + describe('accessibility', () => { + const mandatoryComponent = makeComponent(true); + const optionalComponent = makeComponent(false); + + test.each([ + ['UnusedComponent', , optionalComponent], + [ + 'SelectedComponent with both nudge buttons', + , + optionalComponent, + ], + [ + 'SelectedComponent at top of list with only nudge down button', + , + optionalComponent, + ], + [ + 'SelectedComponent at bottom of list with only nudge up button', + , + optionalComponent, + ], + [ + 'mandatory SelectedComponent', + , + mandatoryComponent, + ], + ['DraggingComponent', , optionalComponent], + ] as const)('has no accessibility violations when rendering %s', async (_description, node, component) => { + const { container } = renderComponent(node, component); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ProfileSettingsEditor/ComponentList.test.tsx b/src/features/manageProfileSettings/components/ProfileSettingsEditor/ComponentList.test.tsx index 02933596a..a584cd2dc 100644 --- a/src/features/manageProfileSettings/components/ProfileSettingsEditor/ComponentList.test.tsx +++ b/src/features/manageProfileSettings/components/ProfileSettingsEditor/ComponentList.test.tsx @@ -2,6 +2,7 @@ import { MemoryRouter } from 'react-router-dom'; import { DndContext } from '@dnd-kit/core'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ComponentType } from './BaseComponent'; import { ComponentList } from './ComponentList'; @@ -48,4 +49,47 @@ describe('ComponentList', () => { expect(screen.getByTestId('selected-component-list')).toBeInTheDocument(); expect(screen.getByText('selected-title-id')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + [ + 'unused list', + + + +
children
+
+
+
, + ], + [ + 'selected list', + + +
children
+
+
, + ], + ])('has no accessibility violations when rendering %s', async (_description, node) => { + const { container } = render(node); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ProfileSettingsEditor/ProfileSettingsEditor.test.tsx b/src/features/manageProfileSettings/components/ProfileSettingsEditor/ProfileSettingsEditor.test.tsx index a08213144..a1fbdfaf6 100644 --- a/src/features/manageProfileSettings/components/ProfileSettingsEditor/ProfileSettingsEditor.test.tsx +++ b/src/features/manageProfileSettings/components/ProfileSettingsEditor/ProfileSettingsEditor.test.tsx @@ -4,6 +4,7 @@ import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ProfileSettingsMode } from '@/common/constants/profileSettings.constants'; import { AdvancedFieldType } from '@/common/constants/uiControls.constants'; @@ -20,7 +21,7 @@ describe('ProfileSettingsEditor', () => { }); const renderComponent = () => { - render( + return render( @@ -253,4 +254,184 @@ describe('ProfileSettingsEditor', () => { expect(mockSetSettingsName).toHaveBeenCalledWith(newName); }); }); + + describe('accessibility', () => { + test.each([ + ['no state', () => {}], + [ + 'creating mode', + () => + setInitialGlobalState([ + { + store: useManageProfileSettingsStore, + state: { + fullProfile: [ + { + id: 'profile', + type: AdvancedFieldType.block, + displayName: 'Profile', + children: ['child'], + }, + { + id: 'child', + type: AdvancedFieldType.simple, + displayName: 'Child', + }, + ], + profileSettings: { + active: false, + children: [], + }, + mode: ProfileSettingsMode.Creating, + }, + }, + ]), + ], + [ + 'editing mode', + () => + setInitialGlobalState([ + { + store: useManageProfileSettingsStore, + state: { + fullProfile: [ + { + id: 'profile', + type: AdvancedFieldType.block, + displayName: 'Profile', + children: ['child'], + }, + { + id: 'child', + type: AdvancedFieldType.simple, + displayName: 'Child', + }, + ], + profileSettings: { + active: false, + children: [], + }, + mode: ProfileSettingsMode.Editing, + }, + }, + ]), + ], + [ + 'inactive settings', + () => + setInitialGlobalState([ + { + store: useManageProfileSettingsStore, + state: { + fullProfile: [ + { + id: 'profile', + type: AdvancedFieldType.block, + displayName: 'Profile', + children: ['child'], + }, + { + id: 'child', + type: AdvancedFieldType.simple, + displayName: 'Child', + }, + ], + profileSettings: { + active: false, + children: [], + }, + mode: ProfileSettingsMode.Creating, + }, + }, + ]), + ], + [ + 'active settings but none in use', + () => + setInitialGlobalState([ + { + store: useManageProfileSettingsStore, + state: { + fullProfile: [ + { + id: 'profile', + type: AdvancedFieldType.block, + displayName: 'Profile', + children: ['child'], + }, + { + id: 'child', + type: AdvancedFieldType.simple, + displayName: 'Child', + }, + ], + profileSettings: { + active: true, + children: [ + { + id: 'child', + visible: false, + }, + ], + }, + mode: ProfileSettingsMode.Creating, + }, + }, + ]), + ], + [ + 'active settings with some in use', + () => + setInitialGlobalState([ + { + store: useManageProfileSettingsStore, + state: { + fullProfile: [ + { + id: 'profile', + type: AdvancedFieldType.block, + displayName: 'Profile', + children: ['childA', 'childB'], + }, + { + id: 'childA', + type: AdvancedFieldType.simple, + displayName: 'Child A', + }, + { + id: 'childB', + type: AdvancedFieldType.simple, + displayName: 'Child B', + }, + ], + profileSettings: { + active: true, + children: [ + { + id: 'childB', + visible: true, + order: 1, + }, + { + id: 'childA', + visible: false, + order: 2, + }, + ], + }, + mode: ProfileSettingsMode.Creating, + }, + }, + ]), + ], + ])('has no accessibility violations when %s', async (_description, setup) => { + setup(); + + const { container } = renderComponent(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ProfileSettingsEditor/ResetComponents.test.tsx b/src/features/manageProfileSettings/components/ProfileSettingsEditor/ResetComponents.test.tsx index 02d639e6b..3ecd69d73 100644 --- a/src/features/manageProfileSettings/components/ProfileSettingsEditor/ResetComponents.test.tsx +++ b/src/features/manageProfileSettings/components/ProfileSettingsEditor/ResetComponents.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useManageProfileSettingsStore } from '@/store'; @@ -59,4 +60,26 @@ describe('ResetComponents', () => { expect(mockResetProfileSettings).toHaveBeenCalled(); }); }); + + describe('accessibility', () => { + test.each([ + ['no changes made to components', false], + ['changes made to components', true], + ])('has no accessibility violations when %s', async (_description, isSettingsActive) => { + setInitialGlobalState([ + { + store: useManageProfileSettingsStore, + state: { + isSettingsActive, + }, + }, + ]); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ProfileSettingsList/ProfileSettingsList.test.tsx b/src/features/manageProfileSettings/components/ProfileSettingsList/ProfileSettingsList.test.tsx index bbf6c2b0a..cb8448169 100644 --- a/src/features/manageProfileSettings/components/ProfileSettingsList/ProfileSettingsList.test.tsx +++ b/src/features/manageProfileSettings/components/ProfileSettingsList/ProfileSettingsList.test.tsx @@ -2,6 +2,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ProfileSettingsMode } from '@/common/constants/profileSettings.constants'; @@ -147,4 +148,14 @@ describe('ProfileSettingsList', () => { expect(mockSetIsPreferredProfileSettings).not.toHaveBeenCalled(); }); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderComponent(false); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/manageProfileSettings/components/ProfilesList/ProfilesList.test.tsx b/src/features/manageProfileSettings/components/ProfilesList/ProfilesList.test.tsx index 94025d2b9..ec60f1258 100644 --- a/src/features/manageProfileSettings/components/ProfilesList/ProfilesList.test.tsx +++ b/src/features/manageProfileSettings/components/ProfilesList/ProfilesList.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { MemoryRouter } from 'react-router-dom'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { fetchProfiles } from '@/common/api/profiles.api'; import { TYPE_URIS } from '@/common/constants/bibframe.constants'; @@ -134,4 +135,20 @@ describe('ProfilesList', () => { expect(firstProfile.parentElement).toHaveClass('selected'); expect(mockSetIsManageProfileSettingsUnsavedModalOpen).toHaveBeenCalledWith(true); }); + + describe('accessibility', () => { + test.each([ + ['pre-set profiles', true, false, false], + ['profiles fail to load', false, false, true], + ])( + 'has no accessibility violations when %s', + async (_description, presetProfiles, isModified, fetchProfileError) => { + const { container } = renderComponent(presetProfiles, isModified, fetchProfileError); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }, + ); + }); }); diff --git a/src/features/preview/components/Preview/ChildFields.test.tsx b/src/features/preview/components/Preview/ChildFields.test.tsx index e321ffa84..a89246a7d 100644 --- a/src/features/preview/components/Preview/ChildFields.test.tsx +++ b/src/features/preview/components/Preview/ChildFields.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { checkShouldGroupWrap } from '@/common/helpers/preview.helper'; @@ -57,4 +58,17 @@ describe('ChildFields', () => { expect(checkShouldGroupWrap).not.toHaveBeenCalled(); }); + + describe('accessibility', () => { + test.each([ + ['no entryChildren provided', { entryChildren: undefined }], + ['isGroupable is true', { isGroupable: true }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/preview/components/Preview/Preview.test.tsx b/src/features/preview/components/Preview/Preview.test.tsx index 45d328a67..6ff4fb285 100644 --- a/src/features/preview/components/Preview/Preview.test.tsx +++ b/src/features/preview/components/Preview/Preview.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { BrowserRouter } from 'react-router-dom'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useInputsStore, useProfileStore, useUIStore } from '@/store'; @@ -42,6 +43,8 @@ const schema = new Map([ describe('Preview', () => { const { getAllByTestId, getByText } = screen; + let container: HTMLElement; + beforeEach(() => { setInitialGlobalState([ { @@ -58,11 +61,11 @@ describe('Preview', () => { }, ]); - return render( + ({ container } = render( , - ); + )); }); test('renders Preview component if a profile is selected', () => { @@ -76,4 +79,12 @@ describe('Preview', () => { test('renders user values if an entry has no children', () => { expect(getByText('uuid1-label')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/preview/components/Preview/TitledPreview.test.tsx b/src/features/preview/components/Preview/TitledPreview.test.tsx index d8759b926..d819a2200 100644 --- a/src/features/preview/components/Preview/TitledPreview.test.tsx +++ b/src/features/preview/components/Preview/TitledPreview.test.tsx @@ -3,6 +3,7 @@ import { navigateToEditPage } from '@/test/__mocks__/common/hooks/useNavigateToE import { BrowserRouter } from 'react-router-dom'; import { fireEvent, render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ITitledPreview, TitledPreview } from './TitledPreview'; @@ -57,4 +58,17 @@ describe('TitledPreview', () => { expect(navigateToEditPage).toHaveBeenCalled(); }); + + describe('accessibility', () => { + test.each([ + ["there's preview content", defaultProps], + ["there's no preview content", { ...defaultProps, previewContent: undefined }], + ])('has no accessibility violations when %s', async (_description, props) => { + const { container } = renderWithProps(props); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/preview/components/PreviewActionsDropdown/PreviewActionsDropdown.test.tsx b/src/features/preview/components/PreviewActionsDropdown/PreviewActionsDropdown.test.tsx index 8aa562b75..d82ee6eb0 100644 --- a/src/features/preview/components/PreviewActionsDropdown/PreviewActionsDropdown.test.tsx +++ b/src/features/preview/components/PreviewActionsDropdown/PreviewActionsDropdown.test.tsx @@ -3,6 +3,7 @@ import { onCreateNewResource } from '@/test/__mocks__/common/hooks/useNavigateTo import { BrowserRouter } from 'react-router-dom'; import { fireEvent, render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { PreviewActionsDropdown } from './PreviewActionsDropdown'; @@ -28,4 +29,18 @@ describe('PreviewActionsDropdown', () => { }, }); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = render( + + + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/profiles/components/ModalChooseProfile/ModalChooseProfile.test.tsx b/src/features/profiles/components/ModalChooseProfile/ModalChooseProfile.test.tsx index 0b1d645d6..d0ff65987 100644 --- a/src/features/profiles/components/ModalChooseProfile/ModalChooseProfile.test.tsx +++ b/src/features/profiles/components/ModalChooseProfile/ModalChooseProfile.test.tsx @@ -1,6 +1,7 @@ import { createModalContainer } from '@/test/__mocks__/common/misc/createModalContainer.mock'; import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ModalChooseProfile } from './ModalChooseProfile'; @@ -333,4 +334,60 @@ describe('ModalChooseProfile', () => { const checkbox = screen.getByRole('checkbox'); expect(checkbox).not.toBeChecked(); }); + + describe('accessibility', () => { + const baseProps = { + isOpen: true, + profileSelectionType: mockProfileSelectionType, + onCancel, + onSubmit, + onClose, + profiles: mockProfiles, + }; + + test.each([ + ['default profile selection', {}], + [ + 'checkbox checked for preferred profile', + { + selectedProfileId: 'profile_1', + preferredProfiles: [ + { id: 'profile_1', name: 'Test Profile 1', resourceType: 'http://bibfra.me/vocab/lite/Work' }, + ], + resourceTypeURL: 'http://bibfra.me/vocab/lite/Work' as ResourceTypeURL, + }, + ], + [ + 'checkbox unchecked for non-preferred profile', + { + selectedProfileId: 'profile_1', + preferredProfiles: [ + { id: 'profile_2', name: 'Test Profile 2', resourceType: 'http://bibfra.me/vocab/lite/Instance' }, + ], + resourceTypeURL: 'http://bibfra.me/vocab/lite/Work' as ResourceTypeURL, + }, + ], + ['no preferred profiles provided', { selectedProfileId: 'profile_1' }], + [ + 'no resourceTypeURL provided', + { + selectedProfileId: 'profile_1', + preferredProfiles: [ + { + id: 'profile_1', + name: 'Test Profile 1', + resourceType: 'http://bibfra.me/vocab/lite/Work' as ResourceTypeURL, + }, + ], + }, + ], + ['isOpen is false', { isOpen: false }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/profiles/components/ModalChooseProfile/WarningMessages.test.tsx b/src/features/profiles/components/ModalChooseProfile/WarningMessages.test.tsx index 4fb5c7d10..a4b96b7d3 100644 --- a/src/features/profiles/components/ModalChooseProfile/WarningMessages.test.tsx +++ b/src/features/profiles/components/ModalChooseProfile/WarningMessages.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as profileSelectionHelper from '@/common/helpers/profileSelection.helper'; @@ -137,4 +138,92 @@ describe('WarningMessages', () => { expect(screen.getByTestId('formatted-message-ld.warning.message_3')).toBeInTheDocument(); expect(profileSelectionHelper.getWarningByProfileNames).toHaveBeenCalledWith('work', 'Monograph', 'Rare Books'); }); + + describe('accessibility', () => { + test.each([ + [ + 'warnings exist', + () => (profileSelectionHelper.getWarningByProfileNames as jest.Mock).mockReturnValue(['ld.warning.message1']), + { + profileSelectionType: mockProfileSelectionType, + profiles: mockProfiles, + selectedProfileId: '1', + selectedValue: '2', + }, + ], + [ + 'no warnings', + () => (profileSelectionHelper.getWarningByProfileNames as jest.Mock).mockReturnValue(null), + { + profileSelectionType: mockProfileSelectionType, + profiles: mockProfiles, + selectedProfileId: '1', + selectedValue: '2', + }, + ], + [ + 'action is not change', + () => {}, + { + profileSelectionType: { ...mockProfileSelectionType, action: 'set' as ProfileSelectionActionType }, + profiles: mockProfiles, + selectedProfileId: '1', + selectedValue: '2', + }, + ], + [ + 'selectedProfileId and selectedValue are the same', + () => {}, + { + profileSelectionType: mockProfileSelectionType, + profiles: mockProfiles, + selectedProfileId: '1', + selectedValue: '1', + }, + ], + [ + 'selectedProfileId is null', + () => {}, + { + profileSelectionType: mockProfileSelectionType, + profiles: mockProfiles, + selectedProfileId: null, + selectedValue: '2', + }, + ], + [ + 'profiles are not found', + () => (profileSelectionHelper.getWarningByProfileNames as jest.Mock).mockReturnValue([]), + { + profileSelectionType: mockProfileSelectionType, + profiles: [] as ProfileDTO[], + selectedProfileId: '1', + selectedValue: '2', + }, + ], + [ + 'multiple warning messages', + () => + (profileSelectionHelper.getWarningByProfileNames as jest.Mock).mockReturnValue([ + 'ld.warning.message_1', + 'ld.warning.message_2', + 'ld.warning.message_3', + ]), + { + profileSelectionType: mockProfileSelectionType, + profiles: mockProfiles, + selectedProfileId: '1', + selectedValue: '3', + }, + ], + ])('has no accessibility violations when %s', async (_description, setup, props) => { + setup(); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/profiles/components/ProfileSelectionManager/ModalWarning.test.tsx b/src/features/profiles/components/ProfileSelectionManager/ModalWarning.test.tsx index 5071729b4..18663f27a 100644 --- a/src/features/profiles/components/ProfileSelectionManager/ModalWarning.test.tsx +++ b/src/features/profiles/components/ProfileSelectionManager/ModalWarning.test.tsx @@ -1,6 +1,7 @@ import { createModalContainer } from '@/test/__mocks__/common/misc/createModalContainer.mock'; import { act, fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ModalWarning } from './ModalWarning'; @@ -65,4 +66,17 @@ describe('ModalWarning', () => { expect(props.onClose).toHaveBeenCalled(); }); + + describe('accessibility', () => { + test.each([ + ['isOpen is false', { isOpen: false }], + ['isOpen is true', {}], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/profiles/components/ProfileSelectionManager/ProfileSelectionManager.test.tsx b/src/features/profiles/components/ProfileSelectionManager/ProfileSelectionManager.test.tsx index dd254d3d4..7099ec697 100644 --- a/src/features/profiles/components/ProfileSelectionManager/ProfileSelectionManager.test.tsx +++ b/src/features/profiles/components/ProfileSelectionManager/ProfileSelectionManager.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen, within } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ROUTES } from '@/common/constants/routes.constants'; @@ -503,4 +504,161 @@ describe('ProfileSelectionManager', () => { // ModalWarning should not be visible expect(screen.queryByTestId('modal-profile-warning')).not.toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['default state', () => {}], + [ + 'modal closed', + () => + setInitialGlobalState([ + { + store: useUIState, + state: { + isProfileSelectionModalOpen: false, + setIsProfileSelectionModalOpen: mockSetIsProfileSelectionModalOpen, + }, + }, + ]), + ], + [ + 'action is "set"', + () => + setInitialGlobalState([ + { + store: useUIState, + state: { + isProfileSelectionModalOpen: true, + setIsProfileSelectionModalOpen: mockSetIsProfileSelectionModalOpen, + profileSelectionType: { + action: 'set', + resourceTypeURL: 'work', + }, + }, + }, + { + store: useProfileState, + state: { + availableProfiles: { + work: [ + { id: 'profile_1', name: 'Test Profile 1', resourceTypeURL: 'work' }, + { id: 'profile_2', name: 'Test Profile 2', resourceTypeURL: 'work' }, + ], + }, + }, + }, + ]), + ], + [ + 'action is "change" with a record profile', + () => + setInitialGlobalState([ + { + store: useUIState, + state: { + isProfileSelectionModalOpen: true, + setIsProfileSelectionModalOpen: mockSetIsProfileSelectionModalOpen, + profileSelectionType: { + action: 'change', + resourceTypeURL: 'work', + }, + }, + }, + { + store: useProfileState, + state: { + availableProfiles: { + work: [ + { id: 'profile_1', name: 'Test Profile 1', resourceTypeURL: 'work' }, + { id: 'profile_2', name: 'Test Profile 2', resourceTypeURL: 'work' }, + ], + }, + }, + }, + { + store: useInputsState, + state: { + record: { profileId: 'profile_2' }, + }, + }, + ]), + ], + [ + 'isEditedRecordChange is true', + () => + setInitialGlobalState([ + { + store: useUIState, + state: { + isProfileSelectionModalOpen: true, + setIsProfileSelectionModalOpen: mockSetIsProfileSelectionModalOpen, + profileSelectionType: { + action: 'change', + resourceTypeURL: 'work', + }, + }, + }, + { + store: useProfileState, + state: { + availableProfiles: { + work: [ + { id: 'profile_1', name: 'Test Profile 1', resourceTypeURL: 'work' }, + { id: 'profile_2', name: 'Test Profile 2', resourceTypeURL: 'work' }, + ], + }, + }, + }, + { + store: useStatusState, + state: { + isRecordEdited: true, + }, + }, + ]), + ], + [ + 'isEditedRecordChange is false', + () => + setInitialGlobalState([ + { + store: useUIState, + state: { + isProfileSelectionModalOpen: true, + setIsProfileSelectionModalOpen: mockSetIsProfileSelectionModalOpen, + profileSelectionType: { + action: 'change', + resourceTypeURL: 'work', + }, + }, + }, + { + store: useProfileState, + state: { + availableProfiles: { + work: [ + { id: 'profile_1', name: 'Test Profile 1', resourceTypeURL: 'work' }, + { id: 'profile_2', name: 'Test Profile 2', resourceTypeURL: 'work' }, + ], + }, + }, + }, + { + store: useStatusState, + state: { + isRecordEdited: false, + }, + }, + ]), + ], + ])('has no accessibility violations when %s', async (_description, setup) => { + setup(); + + const { container } = renderWithProviders(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/AdvancedSearchModal/AdvancedSearchModal.test.tsx b/src/features/search/ui/components/AdvancedSearchModal/AdvancedSearchModal.test.tsx index 2a3ced559..448282d53 100644 --- a/src/features/search/ui/components/AdvancedSearchModal/AdvancedSearchModal.test.tsx +++ b/src/features/search/ui/components/AdvancedSearchModal/AdvancedSearchModal.test.tsx @@ -4,6 +4,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { RouterProvider, createMemoryRouter } from 'react-router-dom'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useSearchStore, useUIStore } from '@/store'; @@ -206,4 +207,14 @@ describe('AdvancedSearchModal', () => { expect(screen.queryByTestId('modal')).not.toBeInTheDocument(); expect(setSearchParams).not.toHaveBeenCalled(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderModal(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/Search/Search.test.tsx b/src/features/search/ui/components/Search/Search.test.tsx index c5bdedc6f..ca37b5aba 100644 --- a/src/features/search/ui/components/Search/Search.test.tsx +++ b/src/features/search/ui/components/Search/Search.test.tsx @@ -1,11 +1,13 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { SearchTypeConfig } from '@/features/search/core/types'; import { useSearchStore } from '@/store'; +import type { SearchProviderProps } from '../../types/provider.types'; import { Search } from './Search'; const mockConfig = { @@ -229,4 +231,55 @@ describe('Search', () => { expect(container).toBeEmptyDOMElement(); }); }); + + describe('accessibility', () => { + test.each([ + [ + 'static mode with required props', + { coreConfig: mockConfig, uiConfig: mockUIConfig, flow: 'url' as const, children:
Search Content
}, + ], + [ + 'static mode="auto"', + { + coreConfig: mockConfig, + uiConfig: mockUIConfig, + flow: 'url' as const, + mode: 'auto' as const, + children:
Auto Mode Content
, + }, + ], + [ + 'static flow="value"', + { + coreConfig: mockConfig, + uiConfig: mockUIConfig, + flow: 'value' as const, + children:
Value Flow Content
, + }, + ], + [ + 'config not provided', + { + coreConfig: undefined as unknown as SearchTypeConfig, + uiConfig: mockUIConfig, + flow: 'url' as const, + children:
Should Not Render
, + }, + ], + [ + 'dynamic mode with segments', + { segments: ['resources', 'hubs'], flow: 'url' as const, children:
Dynamic Mode Content
}, + ], + [ + 'dynamic mode with empty segments', + { segments: [] as string[], flow: 'url' as const, children:
Should Not Render
}, + ], + ] as [string, SearchProviderProps][])('has no accessibility violations when %s', async (_description, props) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchContent/SearchContent.test.tsx b/src/features/search/ui/components/SearchContent/SearchContent.test.tsx index 402b2051b..826446d17 100644 --- a/src/features/search/ui/components/SearchContent/SearchContent.test.tsx +++ b/src/features/search/ui/components/SearchContent/SearchContent.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { DOM_ELEMENTS } from '@/common/constants/domElementsIdentifiers.constants'; @@ -48,4 +49,25 @@ describe('SearchContent', () => { expect(wrapper).toBeInTheDocument(); expect(wrapper).toHaveClass(DOM_ELEMENTS.classNames.itemSearchContent); }); + + describe('accessibility', () => { + test.each([ + ['single child',
Test Content
], + [ + 'multiple children', + <> +
First Child
+
Second Child
+
Third Child
+ , + ], + ['no children', null], + ])('has no accessibility violations when %s', async (_description, children) => { + const { container } = render({children}); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchContent/SearchContentContainer.test.tsx b/src/features/search/ui/components/SearchContent/SearchContentContainer.test.tsx index 85c03c0f1..de28b0820 100644 --- a/src/features/search/ui/components/SearchContent/SearchContentContainer.test.tsx +++ b/src/features/search/ui/components/SearchContent/SearchContentContainer.test.tsx @@ -1,6 +1,7 @@ import { MemoryRouter } from 'react-router-dom'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as SearchHooks from '../../hooks'; import * as SearchProvider from '../../providers'; @@ -181,4 +182,57 @@ describe('SearchContentContainer', () => { expect(placeholder).toBeInTheDocument(); expect(screen.queryByText('Should Not Render')).not.toBeInTheDocument(); }); + + interface AccessibilityVariant { + context: Record; + query: string; + children?: React.ReactNode; + message?: string; + emptyPlaceholderClassName?: string; + } + + describe('accessibility', () => { + test.each([ + [ + 'data exists', + { + context: { results: { items: [{ id: '1', title: 'Test' }], totalRecords: 1 } }, + query: 'test query', + children:
Search Results
, + }, + ], + ['no data and no message', { context: { results: undefined }, query: '' }], + ['message provided', { context: { results: undefined }, query: '', message: 'ld.noResults' }], + [ + 'custom empty placeholder className', + { context: { results: undefined }, query: '', emptyPlaceholderClassName: 'custom-empty-class' }, + ], + [ + 'empty array data', + { + context: { results: { items: [], totalRecords: 0 } }, + query: '', + children:
Should Not Render
, + }, + ], + ] as [string, AccessibilityVariant][])( + 'has no accessibility violations when %s', + async (_description, { context, query, children, message, emptyPlaceholderClassName }) => { + mockUseSearchContext(context); + mockUseCommittedSearchParams(query); + + const { container } = render( + + + {children} + + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }, + ); + }); }); diff --git a/src/features/search/ui/components/SearchControlPane/ControlPane.test.tsx b/src/features/search/ui/components/SearchControlPane/ControlPane.test.tsx index 0801e497c..38d28312a 100644 --- a/src/features/search/ui/components/SearchControlPane/ControlPane.test.tsx +++ b/src/features/search/ui/components/SearchControlPane/ControlPane.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { IntlProvider } from 'react-intl'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useUIStore } from '@/store'; @@ -122,4 +123,49 @@ describe('ControlPane', () => { expect(controlPane).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['children provided', { children:
Test Child
}], + ['label provided', { label: 'Test Label' }], + ['subLabel shown', { label: 'Test Label', subLabel: Test SubLabel, showSubLabel: true }], + ['subLabel hidden', { subLabel: Test SubLabel, showSubLabel: false }], + ['custom close button', { renderCloseButton: () => }], + ['label as ReactElement', { label: Custom Label }], + ])('has no accessibility violations when %s', async (_description, props) => { + setInitialGlobalState([ + { + store: useUIStore, + state: { + isSearchPaneCollapsed: false, + setIsSearchPaneCollapsed: mockSetIsSearchPaneCollapsed, + }, + }, + ]); + + const { container } = renderWithIntl(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + + test('has no accessibility violations when pane is collapsed', async () => { + setInitialGlobalState([ + { + store: useUIStore, + state: { + isSearchPaneCollapsed: true, + setIsSearchPaneCollapsed: mockSetIsSearchPaneCollapsed, + }, + }, + ]); + + const { container } = renderWithIntl(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchControlPane/SearchControlPane.test.tsx b/src/features/search/ui/components/SearchControlPane/SearchControlPane.test.tsx index ec50aac56..a07f70d7f 100644 --- a/src/features/search/ui/components/SearchControlPane/SearchControlPane.test.tsx +++ b/src/features/search/ui/components/SearchControlPane/SearchControlPane.test.tsx @@ -1,6 +1,9 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; +import { ComponentProps } from 'react'; + import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useSearchStore, useUIStore } from '@/store'; @@ -379,4 +382,86 @@ describe('SearchControlPane', () => { const formattedMessage = screen.getByTestId('formatted-message-ld.recordsFound'); expect(formattedMessage).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['with label', { componentProps: { label: 'Test Label' } }], + ['with ReactElement label', { componentProps: { label:
Custom Label
} }], + ['with children', { componentProps: { label: 'Test', children:
Child Content
} }], + [ + 'with sublabel shown', + { + contextOverride: { + activeUIConfig: { + ui: { titleId: undefined, subtitleId: undefined }, + features: { isVisibleSubLabel: false }, + }, + results: { totalRecords: 25 }, + }, + componentProps: { + label: 'Test', + showSubLabel: true, + renderSubLabel: (count: number) => Total: {count}, + }, + }, + ], + ['without label', { componentProps: {} }], + [ + 'with context titleId', + { + contextOverride: { + activeUIConfig: { + ui: { titleId: 'ld.resources', subtitleId: undefined }, + features: { isVisibleSubLabel: false }, + }, + }, + componentProps: {}, + }, + ], + [ + 'with subtitle from context', + { + contextOverride: { + activeUIConfig: { + ui: { titleId: 'ld.resources', subtitleId: 'ld.recordsFound' }, + features: { isVisibleSubLabel: true }, + }, + results: { totalRecords: 42 }, + }, + componentProps: {}, + }, + ], + ] as [ + string, + { contextOverride?: Record; componentProps: ComponentProps }, + ][])('has no accessibility violations when %s', async (_description, { contextOverride, componentProps }) => { + if (contextOverride) { + mockUseSearchContext.mockReturnValue(contextOverride); + } + + setInitialGlobalState([ + { + store: useSearchStore, + state: { + pageMetadata: { + totalElements: 25, + }, + }, + }, + { + store: useUIStore, + state: { + isSearchPaneCollapsed: false, + setIsSearchPaneCollapsed: mockSetIsSearchPaneCollapsed, + }, + }, + ]); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchControls/InputsWrapper.test.tsx b/src/features/search/ui/components/SearchControls/InputsWrapper.test.tsx index c1d109359..78c3f5426 100644 --- a/src/features/search/ui/components/SearchControls/InputsWrapper.test.tsx +++ b/src/features/search/ui/components/SearchControls/InputsWrapper.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { InputsWrapper } from './InputsWrapper'; @@ -55,4 +56,14 @@ describe('InputsWrapper', () => { const wrapper = container.querySelector('.inputs'); expect(wrapper?.tagName).toBe('DIV'); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchControls/MetaControls.test.tsx b/src/features/search/ui/components/SearchControls/MetaControls.test.tsx index 7478f406f..e86afeff7 100644 --- a/src/features/search/ui/components/SearchControls/MetaControls.test.tsx +++ b/src/features/search/ui/components/SearchControls/MetaControls.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useUIStore } from '@/store'; @@ -201,4 +202,24 @@ describe('MetaControls', () => { const wrapper = container.querySelector('.meta-controls'); expect(wrapper?.tagName).toBe('DIV'); }); + + describe('accessibility', () => { + test.each([ + ['advanced search closed', { isAdvancedSearchOpen: false, setIsAdvancedSearchOpen: mockSetIsAdvancedSearchOpen }], + ['advanced search open', { isAdvancedSearchOpen: true, setIsAdvancedSearchOpen: mockSetIsAdvancedSearchOpen }], + ])('has no accessibility violations when %s', async (_description, state) => { + setInitialGlobalState([ + { + store: useUIStore, + state, + }, + ]); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchControls/QueryInput.test.tsx b/src/features/search/ui/components/SearchControls/QueryInput.test.tsx index 0067c6ce2..fffa819bb 100644 --- a/src/features/search/ui/components/SearchControls/QueryInput.test.tsx +++ b/src/features/search/ui/components/SearchControls/QueryInput.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useSearchStore } from '@/store'; @@ -165,4 +166,24 @@ describe('QueryInput', () => { const input = screen.getByTestId('id-search-input'); expect(input).toHaveAttribute('aria-label'); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + setInitialGlobalState([ + { + store: useSearchStore, + state: { + query: 'test query', + setQuery: jest.fn(), + }, + }, + ]); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchControls/ResetButton.test.tsx b/src/features/search/ui/components/SearchControls/ResetButton.test.tsx index bc47fa742..231a76211 100644 --- a/src/features/search/ui/components/SearchControls/ResetButton.test.tsx +++ b/src/features/search/ui/components/SearchControls/ResetButton.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { MemoryRouter } from 'react-router-dom'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useSearchStore } from '@/store'; @@ -224,4 +225,32 @@ describe('ResetButton', () => { const button = screen.getByTestId('id-search-reset-button'); expect(button).toHaveAttribute('aria-label'); }); + + describe('accessibility', () => { + test.each([ + ['query exists', 'test query', ['/']], + ['query is empty', '', ['/']], + ['advanced search is active', '', ['/?query=title+adj+value']], + ['query empty and no advanced search active', '', ['/?query=some+query&searchBy=title']], + ])('has no accessibility violations when %s', async (_description, query, initialEntries) => { + setInitialGlobalState([ + { + store: useSearchStore, + state: { + query, + }, + }, + ]); + + const { container } = render( + + + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchControls/RootControls.test.tsx b/src/features/search/ui/components/SearchControls/RootControls.test.tsx index 733a45c55..85d44524e 100644 --- a/src/features/search/ui/components/SearchControls/RootControls.test.tsx +++ b/src/features/search/ui/components/SearchControls/RootControls.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useUIStore } from '@/store'; @@ -257,4 +258,33 @@ describe('RootControls', () => { expect(container.querySelector('.search-pane-content')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['not collapsed', false, {}], + ['collapsed', true, {}], + [ + 'showFilters with filtersComponent', + false, + { showFilters: true, filtersComponent:
Filters
}, + ], + ['custom className', false, { className: 'custom-class' }], + ])('has no accessibility violations when %s', async (_description, isSearchPaneCollapsed, props) => { + setInitialGlobalState([ + { + store: useUIStore, + state: { + isSearchPaneCollapsed, + setIsSearchPaneCollapsed: mockSetIsSearchPaneCollapsed, + }, + }, + ]); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchControls/SearchBySelect.test.tsx b/src/features/search/ui/components/SearchControls/SearchBySelect.test.tsx index f04521321..1c244bb6f 100644 --- a/src/features/search/ui/components/SearchControls/SearchBySelect.test.tsx +++ b/src/features/search/ui/components/SearchControls/SearchBySelect.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useSearchStore } from '@/store'; @@ -131,4 +132,27 @@ describe('SearchBySelect', () => { const select = screen.getByTestId('id-search-select'); expect(select).toHaveAttribute('id', 'id-search-select'); }); + + describe('accessibility', () => { + test.each([ + ['searchBy is "keyword"', 'keyword'], + ['searchBy is "title"', 'title'], + ])('has no accessibility violations when %s', async (_description, searchBy) => { + setInitialGlobalState([ + { + store: useSearchStore, + state: { + searchBy, + setSearchBy: jest.fn(), + }, + }, + ]); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchControls/Segment.test.tsx b/src/features/search/ui/components/SearchControls/Segment.test.tsx index 8c716e509..f04ebe228 100644 --- a/src/features/search/ui/components/SearchControls/Segment.test.tsx +++ b/src/features/search/ui/components/SearchControls/Segment.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { SearchParam } from '@/features/search/core'; @@ -243,4 +244,40 @@ describe('Segment', () => { expect(mockOnSegmentChange).not.toHaveBeenCalled(); }); }); + + describe('accessibility', () => { + test.each([ + ['renders button with derived label from path', '', {}, { path: 'resources' }], + ['renders button with custom labelId', '', {}, { path: 'resources', labelId: 'custom.label' }], + [ + 'renders custom children instead of label', + '', + {}, + { path: 'resources', children: Custom Content }, + ], + ['derives label from composite path', '', {}, { path: 'authorities:search' }], + ['active segment is highlighted', 'resources', { [SearchParam.SEGMENT]: 'resources' }, { path: 'resources' }], + ])( + 'has no accessibility violations when %s', + async (_description, currentSegment, navigationStateOverrides, props) => { + mockCurrentSegment = currentSegment; + setInitialGlobalState([ + { + store: useSearchStore, + state: { navigationState: navigationStateOverrides, setNavigationState }, + }, + ]); + + const { container } = render( +
+ +
, + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }, + ); + }); }); diff --git a/src/features/search/ui/components/SearchControls/SegmentContent.test.tsx b/src/features/search/ui/components/SearchControls/SegmentContent.test.tsx index 7dfa546ba..6094d47cc 100644 --- a/src/features/search/ui/components/SearchControls/SegmentContent.test.tsx +++ b/src/features/search/ui/components/SearchControls/SegmentContent.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { SearchParam } from '@/features/search/core'; @@ -193,4 +194,38 @@ describe('SegmentContent', () => { expect(container.firstChild).toBeNull(); }); }); + + describe('accessibility', () => { + test.each([ + ['segment matches exactly', 'resources', { [SearchParam.SEGMENT]: 'resources' }, { segment: 'resources' }], + [ + 'prefix matches', + 'authorities:search', + { [SearchParam.SEGMENT]: 'authorities:search' }, + { segment: 'authorities', matchPrefix: true }, + ], + ['segment does not match', 'authorities', { [SearchParam.SEGMENT]: 'authorities' }, { segment: 'resources' }], + ])( + 'has no accessibility violations when %s', + async (_description, currentSegment, navigationStateOverrides, props) => { + mockCurrentSegment = currentSegment; + setInitialGlobalState([ + { + store: useSearchStore, + state: { navigationState: navigationStateOverrides, setNavigationState }, + }, + ]); + + const { container } = render( + +
Content
+
, + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }, + ); + }); }); diff --git a/src/features/search/ui/components/SearchControls/SegmentGroup.test.tsx b/src/features/search/ui/components/SearchControls/SegmentGroup.test.tsx index 7fcb36c51..c27de5e85 100644 --- a/src/features/search/ui/components/SearchControls/SegmentGroup.test.tsx +++ b/src/features/search/ui/components/SearchControls/SegmentGroup.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { SearchParam } from '@/features/search/core'; @@ -149,4 +150,44 @@ describe('SegmentGroup', () => { expect(group).toBeInTheDocument(); expect(group?.classList).toContain('search-segments'); }); + + describe('accessibility', () => { + test.each([ + ['no parentPath specified', '', {}, {}], + [ + 'current segment matches parent path', + 'authorities', + { [SearchParam.SEGMENT]: 'authorities' }, + { parentPath: 'authorities' }, + ], + [ + 'current segment does not match parent path', + 'resources', + { [SearchParam.SEGMENT]: 'resources' }, + { parentPath: 'authorities' }, + ], + ['custom className provided', '', {}, { className: 'custom-class' }], + ])( + 'has no accessibility violations when %s', + async (_description, currentSegment, navigationStateOverrides, props) => { + mockCurrentSegment = currentSegment; + setInitialGlobalState([ + { + store: useSearchStore, + state: { navigationState: navigationStateOverrides, setNavigationState }, + }, + ]); + + const { container } = render( + + + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }, + ); + }); }); diff --git a/src/features/search/ui/components/SearchControls/SourceSelector.test.tsx b/src/features/search/ui/components/SearchControls/SourceSelector.test.tsx index 40642ea2e..2c8f51ba1 100644 --- a/src/features/search/ui/components/SearchControls/SourceSelector.test.tsx +++ b/src/features/search/ui/components/SearchControls/SourceSelector.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { SearchParam } from '@/features/search/core'; @@ -254,4 +255,41 @@ describe('SourceSelector', () => { expect(() => fireEvent.click(externalRadio)).not.toThrow(); }); }); + + describe('accessibility', () => { + test.each([ + ['default props', {}, { options: mockOptions, defaultValue: 'local' }], + [ + 'custom accordion props', + {}, + { + options: mockOptions, + defaultValue: 'local', + accordionId: 'custom-id', + accordionTitleId: 'custom.title', + groupId: 'custom-group', + }, + ], + ['no defaultValue provided', {}, { options: mockOptions }], + [ + 'navigationState source overrides defaultValue', + { [SearchParam.SOURCE]: 'external' }, + { options: mockOptions, defaultValue: 'local' }, + ], + ['empty options array', {}, { options: [], defaultValue: 'local' }], + ])('has no accessibility violations when %s', async (_description, navigationState, props) => { + setInitialGlobalState([ + { + store: useSearchStore, + state: { navigationState }, + }, + ]); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchControls/SubmitButton.test.tsx b/src/features/search/ui/components/SearchControls/SubmitButton.test.tsx index d86a90fd2..552775c46 100644 --- a/src/features/search/ui/components/SearchControls/SubmitButton.test.tsx +++ b/src/features/search/ui/components/SearchControls/SubmitButton.test.tsx @@ -1,6 +1,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useSearchStore } from '@/store'; @@ -119,4 +120,26 @@ describe('SubmitButton', () => { const button = screen.getByTestId('id-search-button'); expect(button).toHaveClass('search-button', 'primary-search'); }); + + describe('accessibility', () => { + test.each([ + ['query exists', 'test query'], + ['query is empty', ''], + ])('has no accessibility violations when %s', async (_description, query) => { + setInitialGlobalState([ + { + store: useSearchStore, + state: { + query, + }, + }, + ]); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchEmptyPlaceholder/SearchEmptyPlaceholder.test.tsx b/src/features/search/ui/components/SearchEmptyPlaceholder/SearchEmptyPlaceholder.test.tsx index 741835b59..63d41424c 100644 --- a/src/features/search/ui/components/SearchEmptyPlaceholder/SearchEmptyPlaceholder.test.tsx +++ b/src/features/search/ui/components/SearchEmptyPlaceholder/SearchEmptyPlaceholder.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { SearchEmptyPlaceholder } from './SearchEmptyPlaceholder'; @@ -38,4 +39,19 @@ describe('SearchEmptyPlaceholder', () => { const placeholderDiv = container.querySelector('.empty-placeholder'); expect(placeholderDiv).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['no props', {}], + ['custom labelId', { labelId: 'ld.enterSearchCriteria' }], + ['custom className', { className: 'custom-class' }], + ['labelId and className', { labelId: 'ld.noResults', className: 'custom-empty' }], + ])('has no accessibility violations when %s', async (_description, props) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchPagination/SearchPagination.test.tsx b/src/features/search/ui/components/SearchPagination/SearchPagination.test.tsx index 7d2d9756f..1fd30dd68 100644 --- a/src/features/search/ui/components/SearchPagination/SearchPagination.test.tsx +++ b/src/features/search/ui/components/SearchPagination/SearchPagination.test.tsx @@ -1,6 +1,7 @@ import { BrowserRouter } from 'react-router-dom'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as UseCommittedSearchParams from '../../hooks/useCommittedSearchParams'; import * as SearchProvider from '../../providers/SearchProvider'; @@ -152,4 +153,28 @@ describe('SearchPagination', () => { expect(screen.getByTestId('pagination')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['data exists', {}, {}, {}], + [ + 'offset and data variant', + { results: { items: [], totalRecords: 50, pageMetadata: { totalElements: 50, totalPages: 5 } } }, + { offset: 20 }, + {}, + ], + ['custom showCount prop', {}, {}, { showCount: false }], + ['custom isLooped prop', {}, {}, { isLooped: true }], + ['pageMetadata is null', { results: { items: [], totalRecords: 0, pageMetadata: null } }, {}, {}], + ])('has no accessibility violations when %s', async (_description, contextOverrides, paramsOverrides, props) => { + mockUseSearchContext(contextOverrides); + mockUseCommittedSearchParams(paramsOverrides); + + const { container } = renderWithRouter(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/SearchResults/SearchResultsRoot.test.tsx b/src/features/search/ui/components/SearchResults/SearchResultsRoot.test.tsx index 2d7d8c0ee..425c6af9f 100644 --- a/src/features/search/ui/components/SearchResults/SearchResultsRoot.test.tsx +++ b/src/features/search/ui/components/SearchResults/SearchResultsRoot.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { SearchResultsRoot } from './SearchResultsRoot'; @@ -64,4 +65,25 @@ describe('SearchResultsRoot', () => { expect(wrapper).toHaveClass('class-one'); expect(wrapper).toHaveClass('class-two'); }); + + describe('accessibility', () => { + test.each([ + ['single child', {},
Content
], + ['custom className', { className: 'custom-class' },
Content
], + [ + 'multiple children', + {}, + <> +
First Child
+
Second Child
+ , + ], + ])('has no accessibility violations when %s', async (_description, props, children) => { + const { container } = render({children}); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/results/authorities/AuthoritiesPageResultList.test.tsx b/src/features/search/ui/components/results/authorities/AuthoritiesPageResultList.test.tsx index 3aaf15e44..de07b240f 100644 --- a/src/features/search/ui/components/results/authorities/AuthoritiesPageResultList.test.tsx +++ b/src/features/search/ui/components/results/authorities/AuthoritiesPageResultList.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as useAuthoritiesPageTableFormatterModule from '@/features/search/ui/hooks/useAuthoritiesPageTableFormatter'; @@ -76,4 +77,14 @@ describe('AuthoritiesPageResultList', () => { expect(mockNavigateWithState).toHaveBeenCalledWith('/linked-data/resources/auth-99/edit'); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/results/authorities/AuthoritiesResultList.test.tsx b/src/features/search/ui/components/results/authorities/AuthoritiesResultList.test.tsx index 68693557e..b7210f3a5 100644 --- a/src/features/search/ui/components/results/authorities/AuthoritiesResultList.test.tsx +++ b/src/features/search/ui/components/results/authorities/AuthoritiesResultList.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as useFormattedResultsHook from '../../../hooks/useFormattedResults'; import * as useTableFormatterHook from '../../../hooks/useTableFormatter'; @@ -200,4 +201,20 @@ describe('AuthoritiesResultList', () => { }), ); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + mockUseFormattedResults.mockReturnValue(mockData); + mockUseTableFormatter.mockReturnValue({ + formattedData: mockFormattedData, + listHeader: mockListHeader, + }); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/results/hubs/HubsLookupResultList.test.tsx b/src/features/search/ui/components/results/hubs/HubsLookupResultList.test.tsx index 4327e68be..3dc00811e 100644 --- a/src/features/search/ui/components/results/hubs/HubsLookupResultList.test.tsx +++ b/src/features/search/ui/components/results/hubs/HubsLookupResultList.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as useFormattedResultsHook from '@/features/search/ui/hooks/useFormattedResults'; import * as useTableFormatterHook from '@/features/search/ui/hooks/useTableFormatter'; @@ -130,4 +131,20 @@ describe('HubsLookupResultList', () => { }), ); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + mockUseFormattedResults.mockReturnValue(mockData); + mockUseTableFormatter.mockReturnValue({ + formattedData: mockFormattedData, + listHeader: mockListHeader, + }); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/results/hubs/HubsResultList.test.tsx b/src/features/search/ui/components/results/hubs/HubsResultList.test.tsx index 4d145be60..b2ec98984 100644 --- a/src/features/search/ui/components/results/hubs/HubsResultList.test.tsx +++ b/src/features/search/ui/components/results/hubs/HubsResultList.test.tsx @@ -2,6 +2,7 @@ import { BrowserRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Row } from '@/components/Table'; @@ -43,7 +44,7 @@ describe('HubsResultList', () => { }; const renderComponent = () => { - render( + return render( @@ -129,4 +130,16 @@ describe('HubsResultList', () => { expect(screen.getByTestId('table-flex')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + jest.spyOn(useHubsTableFormatterModule, 'useHubsTableFormatter').mockReturnValue(mockFormatterReturn); + + const { container } = renderComponent(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/results/resources/ResourcesResultList.test.tsx b/src/features/search/ui/components/results/resources/ResourcesResultList.test.tsx index 9f1ffed5a..3f7921f82 100644 --- a/src/features/search/ui/components/results/resources/ResourcesResultList.test.tsx +++ b/src/features/search/ui/components/results/resources/ResourcesResultList.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as useFormattedResultsHook from '../../../hooks/useFormattedResults'; import { ResourcesResultList } from './ResourcesResultList'; @@ -90,4 +91,25 @@ describe('ResourcesResultList', () => { expect(screen.getByTestId('result-entry-123')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + [ + 'entries exist', + [ + { id: '1', title: 'Test Work 1', author: 'Author 1' } as WorkAsSearchResultDTO, + { id: '2', title: 'Test Work 2', author: 'Author 2' } as WorkAsSearchResultDTO, + ], + ], + ['no data', undefined], + ])('has no accessibility violations when %s', async (_description, data) => { + mockUseFormattedResults.mockReturnValue(data); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/components/results/resources/SearchResultEntry.test.tsx b/src/features/search/ui/components/results/resources/SearchResultEntry.test.tsx index 84d37ebbc..79e4dc804 100644 --- a/src/features/search/ui/components/results/resources/SearchResultEntry.test.tsx +++ b/src/features/search/ui/components/results/resources/SearchResultEntry.test.tsx @@ -4,6 +4,7 @@ import { BrowserRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { useSearchStore, useUIStore } from '@/store'; @@ -205,4 +206,38 @@ describe('SearchResultEntry', () => { expect(getByText('ld.noInstancesAvailable')).toBeInTheDocument(); }); }); + + describe('accessibility', () => { + beforeEach(() => { + setInitialGlobalState([ + { + store: useSearchStore, + state: { + data: [], + query: '', + searchBy: 'keyword', + selectedInstances: [], + setSelectedInstances: jest.fn(), + }, + }, + { + store: useUIStore, + state: { + isSearchPaneCollapsed: false, + }, + }, + ]); + }); + + test.each([ + ['with instances', mockProps], + ['without instances', { ...mockProps, instances: [] }], + ])('has no accessibility violations when %s', async (_description, props) => { + const { container } = renderWithProviders(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/authorities/AssignFormatter.test.tsx b/src/features/search/ui/formatters/authorities/AssignFormatter.test.tsx index 57549ffb6..96fcce550 100644 --- a/src/features/search/ui/formatters/authorities/AssignFormatter.test.tsx +++ b/src/features/search/ui/formatters/authorities/AssignFormatter.test.tsx @@ -1,5 +1,6 @@ import { fireEvent } from '@testing-library/dom'; import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { AuthRefType } from '@/common/constants/search.constants'; @@ -63,4 +64,34 @@ describe('AssignFormatter', () => { expect(queryByTestId('assign-button-2')).toBeNull(); }); + + describe('accessibility', () => { + const authorizedRow = { + authorized: { label: AuthRefType.Authorized }, + __meta: { id: '1' }, + title: { label: 'Title 1' }, + subclass: { label: 'Subclass 1' }, + }; + const unauthorizedRow = { + authorized: { label: AuthRefType.AuthRef }, + __meta: { id: '2' }, + title: { label: 'Title 2' }, + subclass: { label: 'Subclass 2' }, + }; + + test.each([ + ['renders disabled Button', authorizedRow, true], + ['does not render Button when not authorized', unauthorizedRow, false], + ])('has no accessibility violations when %s', async (_description, testRow, checkFailedIdResult) => { + mockCheckFailedId.mockReturnValue(checkFailedIdResult); + + const { container } = render( + AssignFormatter({ row: testRow, onAssign: mockOnAssign, checkFailedId: mockCheckFailedId }), + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/authorities/AuthorityActionFormatter.test.tsx b/src/features/search/ui/formatters/authorities/AuthorityActionFormatter.test.tsx index 584972c5b..2462be5a1 100644 --- a/src/features/search/ui/formatters/authorities/AuthorityActionFormatter.test.tsx +++ b/src/features/search/ui/formatters/authorities/AuthorityActionFormatter.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { AuthorityActionFormatter } from './AuthorityActionFormatter'; @@ -84,4 +85,22 @@ describe('AuthorityActionFormatter', () => { expect(() => fireEvent.click(screen.getByTestId('authority-import-marc-safe'))).not.toThrow(); }); + + describe('accessibility', () => { + test.each([ + ['LD authority with edit button', { row: makeRow('ld-1', true), onEdit: mockOnEdit, onImport: mockOnImport }], + ['LD authority without import button', { row: makeRow('ld-3', true), onEdit: mockOnEdit }], + [ + 'MARC authority with import/edit button', + { row: makeRow('marc-1', false), onEdit: mockOnEdit, onImport: mockOnImport }, + ], + ['MARC authority without edit button', { row: makeRow('marc-3', false), onImport: mockOnImport }], + ])('has no accessibility violations when %s', async (_description, props) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/authorities/AuthorityLabelFormatter.test.tsx b/src/features/search/ui/formatters/authorities/AuthorityLabelFormatter.test.tsx index 493814c7c..0a731627f 100644 --- a/src/features/search/ui/formatters/authorities/AuthorityLabelFormatter.test.tsx +++ b/src/features/search/ui/formatters/authorities/AuthorityLabelFormatter.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { AuthorityLabelFormatter } from './AuthorityLabelFormatter'; @@ -35,4 +36,18 @@ describe('AuthorityLabelFormatter', () => { const button = screen.getByRole('button'); expect(() => fireEvent.click(button)).not.toThrow(); }); + + describe('accessibility', () => { + test.each([ + ['title is absent', makeRow('')], + ['title is present', makeRow('Shakespeare, William', 'auth-42')], + ['row id is set', makeRow('Some Title', 'auth-99')], + ])('has no accessibility violations when %s', async (_description, row) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/authorities/AuthoritySourceFormatter.test.tsx b/src/features/search/ui/formatters/authorities/AuthoritySourceFormatter.test.tsx index 8056209f8..d14c9fecf 100644 --- a/src/features/search/ui/formatters/authorities/AuthoritySourceFormatter.test.tsx +++ b/src/features/search/ui/formatters/authorities/AuthoritySourceFormatter.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { AuthoritySourceFormatter } from './AuthoritySourceFormatter'; @@ -52,4 +53,37 @@ describe('AuthoritySourceFormatter', () => { expect(container.querySelector('span')).toHaveTextContent('MeSH'); }); + + describe('accessibility', () => { + test.each([ + ['label is empty', makeRow()], + [ + 'LD authority', + makeRow({ + __meta: { id: 'ld-1', key: 'k', isAnchor: false, isLD: true }, + source: { label: 'ld.source.linkedData' }, + }), + ], + [ + 'MARC authority with text label', + makeRow({ + __meta: { id: 'marc-1', key: 'k', isAnchor: false, isLD: false }, + source: { label: 'LC Name Authority File' }, + }), + ], + [ + 'MARC authority with plain span', + makeRow({ + __meta: { id: 'marc-2', key: 'k', isAnchor: false, isLD: false }, + source: { label: 'MeSH' }, + }), + ], + ])('has no accessibility violations when %s', async (_description, row) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/authorities/AuthorizedFormatter.test.tsx b/src/features/search/ui/formatters/authorities/AuthorizedFormatter.test.tsx index a0b384476..b527923f7 100644 --- a/src/features/search/ui/formatters/authorities/AuthorizedFormatter.test.tsx +++ b/src/features/search/ui/formatters/authorities/AuthorizedFormatter.test.tsx @@ -1,4 +1,5 @@ import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { AuthRefType } from '@/common/constants/search.constants'; @@ -32,4 +33,17 @@ describe('AuthorizedFormatter', () => { expect(labelElement.tagName.toLowerCase()).toBe('span'); }); + + describe('accessibility', () => { + test.each([ + ['authorized', { ...defaultRow, authorized: { label: AuthRefType.Authorized } }], + ['not authorized', { ...defaultRow, authorized: { label: 'Not Authorized' } }], + ])('has no accessibility violations when %s', async (_description, row) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/authorities/TitleFormatter.test.tsx b/src/features/search/ui/formatters/authorities/TitleFormatter.test.tsx index 86a58bbbe..edc03535b 100644 --- a/src/features/search/ui/formatters/authorities/TitleFormatter.test.tsx +++ b/src/features/search/ui/formatters/authorities/TitleFormatter.test.tsx @@ -1,5 +1,6 @@ import { fireEvent } from '@testing-library/dom'; import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { TitleFormatter } from './TitleFormatter'; @@ -49,4 +50,26 @@ describe('TitleFormatter', () => { expect(onTitleClick).toHaveBeenCalledWith(row.__meta.id, row.title.label, row.subclass.label); }); + + describe('accessibility', () => { + test.each([ + [ + 'isMissingMatchQuery is true', + { + ...defaultRow, + __meta: { ...defaultRow.__meta, isAnchor: true }, + subclass: { label: '' }, + authorized: { label: '' }, + authoritySource: { label: '' }, + }, + ], + ['isMissingMatchQuery is false', { ...defaultRow, __meta: { ...defaultRow.__meta, isAnchor: false } }], + ])('has no accessibility violations when %s', async (_description, row) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/hubs/BaseNotesFormatter.test.tsx b/src/features/search/ui/formatters/hubs/BaseNotesFormatter.test.tsx index 354a122d3..7ddf88632 100644 --- a/src/features/search/ui/formatters/hubs/BaseNotesFormatter.test.tsx +++ b/src/features/search/ui/formatters/hubs/BaseNotesFormatter.test.tsx @@ -1,6 +1,7 @@ import { IntlProvider } from 'react-intl'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { BaseNotesFormatter } from './BaseNotesFormatter'; @@ -105,4 +106,23 @@ describe('BaseNotesFormatter', () => { expect(screen.getByText('-')).toBeInTheDocument(); }); }); + + describe('accessibility', () => { + test.each([ + ['auth label exists', mockRowWithAuthLabel, 'auth'], + ['auth label is undefined', mockRowWithoutAuthLabel, 'auth'], + ['auth label is null', mockRowWithNullLabels, 'auth'], + ['auth object is empty', mockRowWithEmptyObject, 'auth'], + ['rda label exists', mockRowWithRdaLabel, 'rda'], + ['rda label is undefined', mockRowWithoutRdaLabel, 'rda'], + ['rda label is null', mockRowWithNullLabels, 'rda'], + ['rda object is empty', mockRowWithEmptyObject, 'rda'], + ] as const)('has no accessibility violations when %s', async (_description, row, fieldKey) => { + const { container } = renderWithIntl(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/hubs/HubActionFormatter.test.tsx b/src/features/search/ui/formatters/hubs/HubActionFormatter.test.tsx index b8587edaf..c44fdae0c 100644 --- a/src/features/search/ui/formatters/hubs/HubActionFormatter.test.tsx +++ b/src/features/search/ui/formatters/hubs/HubActionFormatter.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { HubActionFormatter } from './HubActionFormatter'; @@ -202,4 +203,39 @@ describe('HubActionFormatter', () => { // Should not throw error expect(importButton).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + [ + 'isLocal is true', + { + __meta: { id: 'token_1', key: 'key_1', isAnchor: false, isLocal: true }, + hub: { label: 'Local Hub', uri: 'http://example.com/hub_1', className: 'hub-title' }, + source: { label: 'ld.source.libraryOfCongress.local', className: 'hub-source' }, + }, + ], + [ + 'isLocal is false', + { + __meta: { id: 'token_2', key: 'key_2', isAnchor: false, isLocal: false }, + hub: { label: 'Remote Hub', uri: 'http://example.com/hub_2', className: 'hub-title' }, + source: { label: 'ld.source.libraryOfCongress', className: 'hub-source' }, + }, + ], + [ + 'isLocal is undefined', + { + __meta: { id: 'token_3', key: 'key_3', isAnchor: false }, + hub: { label: 'Hub Without isLocal', uri: 'http://example.com/hub_3', className: 'hub-title' }, + source: { label: 'ld.source.libraryOfCongress', className: 'hub-source' }, + }, + ], + ])('has no accessibility violations when %s', async (_description, row) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/hubs/HubAssignFormatter.test.tsx b/src/features/search/ui/formatters/hubs/HubAssignFormatter.test.tsx index f1e6f74c3..5f7ea3588 100644 --- a/src/features/search/ui/formatters/hubs/HubAssignFormatter.test.tsx +++ b/src/features/search/ui/formatters/hubs/HubAssignFormatter.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { HubAssignFormatter } from './HubAssignFormatter'; @@ -170,4 +171,17 @@ describe('HubAssignFormatter', () => { true, ); }); + + describe('accessibility', () => { + test.each([ + ['external hub', defaultRow], + ['local hub', { ...defaultRow, __meta: { ...defaultRow.__meta, isLocal: true } }], + ])('has no accessibility violations when %s', async (_description, row) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/hubs/HubLinkFormatter.test.tsx b/src/features/search/ui/formatters/hubs/HubLinkFormatter.test.tsx index db413dd15..caa8d2ce5 100644 --- a/src/features/search/ui/formatters/hubs/HubLinkFormatter.test.tsx +++ b/src/features/search/ui/formatters/hubs/HubLinkFormatter.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { HubLinkFormatter } from './HubLinkFormatter'; @@ -130,4 +131,22 @@ describe('HubLinkFormatter', () => { expect(span?.tagName.toLowerCase()).toBe('span'); expect(screen.queryByRole('link')).not.toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['title and URI are present', defaultRow], + ['title is missing', { ...defaultRow, hub: { label: '', uri: 'test_hub_url' } }], + ['URI is missing', { ...defaultRow, hub: { label: 'Test Hub Label', uri: '' } }], + ['title and URI are missing', { ...defaultRow, hub: { label: '', uri: '' } }], + ['hub data is undefined', { ...defaultRow, hub: { label: undefined, uri: undefined } }], + ['title only, URI is undefined', { ...defaultRow, hub: { label: 'Title Only', uri: undefined } }], + ['URI only, title is undefined', { ...defaultRow, hub: { label: undefined, uri: 'test_hub_url_2' } }], + ])('has no accessibility violations when %s', async (_description, row) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/hubs/HubSourceFormatter.test.tsx b/src/features/search/ui/formatters/hubs/HubSourceFormatter.test.tsx index a6b570a20..55bef2d29 100644 --- a/src/features/search/ui/formatters/hubs/HubSourceFormatter.test.tsx +++ b/src/features/search/ui/formatters/hubs/HubSourceFormatter.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { HubSourceFormatter } from './HubSourceFormatter'; @@ -104,4 +105,39 @@ describe('HubSourceFormatter', () => { expect(screen.getByTestId('hub-source-unique-test-id')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + [ + 'label is a string', + { + __meta: { id: 'token_1', key: 'key_1', isAnchor: false }, + hub: { label: 'Test Hub', uri: 'http://example.com/hub_1', className: 'hub-title' }, + source: { label: 'ld.source.libraryOfCongress', className: 'hub-source' }, + }, + ], + [ + 'label is a local source translation key', + { + __meta: { id: 'token_2', key: 'key_2', isAnchor: false }, + hub: { label: 'Local Hub', uri: 'http://example.com/hub_2', className: 'hub-title' }, + source: { label: 'ld.source.libraryOfCongress.local', className: 'hub-source' }, + }, + ], + [ + 'label is not a string', + { + __meta: { id: 'token_3', key: 'key_3', isAnchor: false }, + hub: { label: 'Test Hub', uri: 'http://example.com/hub_3', className: 'hub-title' }, + source: { label: Custom Label, className: 'hub-source' }, + }, + ], + ])('has no accessibility violations when %s', async (_description, row) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/hubs/RDANotesFormatter.test.tsx b/src/features/search/ui/formatters/hubs/RDANotesFormatter.test.tsx index 817c9d1be..582d69f0e 100644 --- a/src/features/search/ui/formatters/hubs/RDANotesFormatter.test.tsx +++ b/src/features/search/ui/formatters/hubs/RDANotesFormatter.test.tsx @@ -1,4 +1,5 @@ import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { RDANotesFormatter } from './RDANotesFormatter'; @@ -74,4 +75,25 @@ describe('RDANotesFormatter', () => { expect(baseFormatter).toHaveAttribute('data-field-key', 'rda'); expect(baseFormatter).toHaveTextContent(JSON.stringify(emptyRow)); }); + + describe('accessibility', () => { + test.each([ + ['default row data', defaultRow], + [ + 'custom row data', + { + __meta: { id: 'custom_id_456' }, + rda: { notes: ['Custom note'], additionalData: 'test' }, + hub: { label: 'Custom Hub', uri: 'test_custom_url' }, + }, + ], + ['empty row data', { __meta: { id: 'empty_id' } }], + ])('has no accessibility violations when %s', async (_description, row) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/instances/InstanceEditCtlFormatter.test.tsx b/src/features/search/ui/formatters/instances/InstanceEditCtlFormatter.test.tsx index d3252973d..f78fee156 100644 --- a/src/features/search/ui/formatters/instances/InstanceEditCtlFormatter.test.tsx +++ b/src/features/search/ui/formatters/instances/InstanceEditCtlFormatter.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { generateEditResourceUrl } from '@/common/helpers/navigation.helper'; import type { Row } from '@/components/Table'; @@ -106,4 +107,16 @@ describe('InstanceEditCtlFormatter', () => { expect(screen.getByRole('button')).toHaveAttribute('data-type', 'primary'); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = render( + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/instances/InstanceSelectCtlFormatter.test.tsx b/src/features/search/ui/formatters/instances/InstanceSelectCtlFormatter.test.tsx index c5a4ffd6f..9cd131a3b 100644 --- a/src/features/search/ui/formatters/instances/InstanceSelectCtlFormatter.test.tsx +++ b/src/features/search/ui/formatters/instances/InstanceSelectCtlFormatter.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import type { Row } from '@/components/Table'; @@ -125,4 +126,24 @@ describe('InstanceSelectCtlFormatter', () => { expect(container.firstChild).toHaveClass('row-select-container'); }); + + describe('accessibility', () => { + test.each([ + ['instance is not selected', []], + ['instance is selected', ['456', '123', '789']], + ])('has no accessibility violations when %s', async (_description, selectedInstances) => { + const { container } = render( + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/features/search/ui/formatters/instances/InstanceTitleFormatter.test.tsx b/src/features/search/ui/formatters/instances/InstanceTitleFormatter.test.tsx index 3f45a1451..a7fee5a96 100644 --- a/src/features/search/ui/formatters/instances/InstanceTitleFormatter.test.tsx +++ b/src/features/search/ui/formatters/instances/InstanceTitleFormatter.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { FullDisplayType } from '@/common/constants/uiElements.constants'; import type { Row } from '@/components/Table'; @@ -160,4 +161,32 @@ describe('InstanceTitleFormatter', () => { expect(screen.getByTestId('preview-button__123')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['default state', { selectedInstances: [], activePreviewIds: [] }], + [ + 'instance is selected and shows comparison index', + { selectedInstances: ['999', '123', '456'], activePreviewIds: ['999', '123'] }, + ], + ['instance is not selected', { selectedInstances: ['999', '456'], activePreviewIds: ['999'] }], + [ + 'fullDisplayComponentType is Comparison', + { + selectedInstances: ['123'], + activePreviewIds: ['123'], + fullDisplayComponentType: FullDisplayType.Comparison, + }, + ], + ['single preview not in comparison mode', { selectedInstances: ['123'], activePreviewIds: ['123'] }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render( + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/Accordion.test.tsx b/src/test/__tests__/components/Accordion.test.tsx index ab47a36e9..86a7ee9da 100644 --- a/src/test/__tests__/components/Accordion.test.tsx +++ b/src/test/__tests__/components/Accordion.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Accordion } from '@/components/Accordion'; @@ -12,4 +13,14 @@ describe('Accordion', () => { expect(queryByTestId('accordion-contents')).toHaveProperty('hidden'); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/App.test.tsx b/src/test/__tests__/components/App.test.tsx index 24b737ca1..005893c63 100644 --- a/src/test/__tests__/components/App.test.tsx +++ b/src/test/__tests__/components/App.test.tsx @@ -1,17 +1,28 @@ import '@/test/__mocks__/common/helpers/pageScrolling.helper.mock'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { App } from '@/App'; jest.mock('@/common/constants/build.constants', () => ({ IS_EMBEDDED_MODE: false })); describe('App', () => { + let container: HTMLElement; + beforeEach(() => { - render(); + ({ container } = render()); }); test('renders Loading (default) component', () => { expect(screen.getByTestId('loading')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/DatePicker.test.tsx b/src/test/__tests__/components/DatePicker.test.tsx index a6c74d1f5..4be33876a 100644 --- a/src/test/__tests__/components/DatePicker.test.tsx +++ b/src/test/__tests__/components/DatePicker.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { DatePicker } from '@/components/DatePicker/DatePicker'; @@ -40,4 +41,19 @@ describe('DatePicker Component', () => { expect(input).toHaveAttribute('name', 'datePicker'); }); + + describe('accessibility', () => { + const baseProps = { id: 'test-date', value: '2024-11-11', onChange: () => {} }; + + test.each([ + ['default', {}], + ['with placeholder and name', { placeholder: 'Select a date', name: 'datePicker' }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/DateRange.test.tsx b/src/test/__tests__/components/DateRange.test.tsx index 1918fadf4..44403a5ac 100644 --- a/src/test/__tests__/components/DateRange.test.tsx +++ b/src/test/__tests__/components/DateRange.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { DateRange } from '@/components/DateRange'; @@ -62,4 +63,17 @@ describe('DateRange Component', () => { expect(mockSubmit).not.toHaveBeenCalled(); }); + + describe('accessibility', () => { + test.each([ + ['with facet', { facet: 'testFacet' }], + ['without facet', {}], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/Dropdown.test.tsx b/src/test/__tests__/components/Dropdown.test.tsx index 3116b8c47..dd7849a1d 100644 --- a/src/test/__tests__/components/Dropdown.test.tsx +++ b/src/test/__tests__/components/Dropdown.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { DropdownItemType } from '@/common/constants/uiElements.constants'; import { Dropdown } from '@/components/Dropdown'; @@ -87,4 +88,37 @@ describe('Dropdown', () => { expect(getByText('item.label')).toBeInTheDocument(); expect(getByText('Custom Component')).toBeInTheDocument(); }); + + describe('accessibility', () => { + const itemsWithContent: DropdownItems = [ + { + id: 'group_1', + labelId: 'groupLabel', + data: [ + { + id: 'testItem_1', + type: DropdownItemType.basic, + labelId: 'item.label', + action: jest.fn(), + }, + { + id: 'testItem_2', + type: DropdownItemType.customComponent, + renderComponent: (key: string | number) =>
Custom Component
, + }, + ], + }, + ]; + + test.each([ + ['empty items', items], + ['basic and custom items', itemsWithContent], + ])('has no accessibility violations when %s', async (_description, dropdownItems) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/DropdownField.test.tsx b/src/test/__tests__/components/DropdownField.test.tsx index 36eb9d77e..9d770612d 100644 --- a/src/test/__tests__/components/DropdownField.test.tsx +++ b/src/test/__tests__/components/DropdownField.test.tsx @@ -1,6 +1,7 @@ import '@/test/__mocks__/lib/react-select.mock'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { DropdownField } from '@/components/DropdownField'; @@ -14,7 +15,7 @@ describe('Dropdown Field', () => { const { getByTestId } = screen; function renderComponent() { - render(); + return render(); } test('triggers onChange', () => { @@ -29,4 +30,14 @@ describe('Dropdown Field', () => { expect(onChangeFn).toHaveBeenCalledWith(options[0], uuid, true); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderComponent(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/Dropzone.test.tsx b/src/test/__tests__/components/Dropzone.test.tsx index 43d161a08..73013774f 100644 --- a/src/test/__tests__/components/Dropzone.test.tsx +++ b/src/test/__tests__/components/Dropzone.test.tsx @@ -1,5 +1,6 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; +import { axe } from 'jest-axe'; import { Dropzone } from '@/components/Dropzone'; @@ -8,13 +9,16 @@ describe('Dropzone', () => { const acceptableFile = new File(['{}'], 'resources.json', { type: 'application/json' }); const rejectableFile = new File([''], 'not-json.txt', { type: 'text/plain' }); + let container: HTMLElement; + beforeEach(() => { let files: File[] = []; + let rerender: ReturnType['rerender']; const setFiles = (f: File[]) => { files = f; rerender(); }; - const { rerender } = render(); + ({ rerender, container } = render()); }); test('renders dropzone', () => { @@ -54,4 +58,12 @@ describe('Dropzone', () => { fireEvent.click(screen.getByTestId('dropzone-file-remove')); expect(screen.queryByTestId('dropzone-file')).not.toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/ErrorBoundary.test.tsx b/src/test/__tests__/components/ErrorBoundary.test.tsx index a2051261e..b9ebf71a2 100644 --- a/src/test/__tests__/components/ErrorBoundary.test.tsx +++ b/src/test/__tests__/components/ErrorBoundary.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ErrorBoundary } from '@/components/ErrorBoundary'; @@ -18,4 +19,23 @@ describe('Error Boundary', () => { expect(screen.getByTestId('errorBoundary')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + console.error = jest.fn(); + const ThrowError = () => { + throw new Error('Test error boundary'); + }; + + const { container } = render( + + + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/Input.test.tsx b/src/test/__tests__/components/Input.test.tsx index a75a9bfab..7666b52d8 100644 --- a/src/test/__tests__/components/Input.test.tsx +++ b/src/test/__tests__/components/Input.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Input } from '@/components/Input'; @@ -9,9 +10,10 @@ describe('Input', () => { const onChange = jest.fn(); const onPressEnter = jest.fn(); let inputElement: HTMLElement; + let container: HTMLElement; beforeEach(() => { - render( + ({ container } = render( { onChange={onChange} onPressEnter={onPressEnter} />, - ); + )); inputElement = screen.getByTestId(testId); }); @@ -43,4 +45,12 @@ describe('Input', () => { expect(onPressEnter).toHaveBeenCalledTimes(2); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/LiteralField.test.tsx b/src/test/__tests__/components/LiteralField.test.tsx index 00d8ff1ac..6050a1592 100644 --- a/src/test/__tests__/components/LiteralField.test.tsx +++ b/src/test/__tests__/components/LiteralField.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { v4 as uuidv4 } from 'uuid'; import { LiteralField } from '@/components/LiteralField'; @@ -10,7 +11,7 @@ const { getByTestId } = screen; describe('Literal Field', () => { function renderComponent() { - render(); + return render(); } test('triggers handleOnChange', () => { @@ -25,4 +26,14 @@ describe('Literal Field', () => { expect(onChangeFn).toHaveBeenCalledWith(uuid, [{ label: event.target.value }]); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderComponent(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/Loading.test.tsx b/src/test/__tests__/components/Loading.test.tsx index ee4da26f5..95af0dd78 100644 --- a/src/test/__tests__/components/Loading.test.tsx +++ b/src/test/__tests__/components/Loading.test.tsx @@ -1,4 +1,5 @@ import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Loading } from '@/components/Loading'; @@ -9,4 +10,14 @@ describe('Loading', () => { expect(container.querySelector('.loader-overlay')).toBeInTheDocument(); expect(container.querySelector('.loader')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/MarcTooltip.test.tsx b/src/test/__tests__/components/MarcTooltip.test.tsx index 926d9e6b2..61479523b 100644 --- a/src/test/__tests__/components/MarcTooltip.test.tsx +++ b/src/test/__tests__/components/MarcTooltip.test.tsx @@ -1,6 +1,7 @@ import { IntlProvider } from 'react-intl'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { MarcMapping, MarcTooltip, MarcTooltipProps } from '@/components/MarcTooltip'; @@ -51,4 +52,21 @@ describe('MarcTooltip', () => { expect(button.parentElement).toHaveClass('custom-class'); expect(button).toHaveAttribute('data-testid', expect.stringContaining('custom-id')); }); + + describe('accessibility', () => { + const mapping: MarcMapping = { Title: '245 $a', Author: '100 $a' }; + + test.each([ + ['mapping undefined', { mapping: undefined }], + ['mapping empty', { mapping: {} }], + ['mapping with entries', { mapping }], + ['custom className and htmlId', { mapping, className: 'custom-class', htmlId: 'custom-id' }], + ])('has no accessibility violations when %s', async (_description, props) => { + const { container } = renderMarcTooltip(props); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/Modal.test.tsx b/src/test/__tests__/components/Modal.test.tsx index f0d27f216..eca7766c3 100644 --- a/src/test/__tests__/components/Modal.test.tsx +++ b/src/test/__tests__/components/Modal.test.tsx @@ -1,6 +1,7 @@ import { createModalContainer } from '@/test/__mocks__/common/misc/createModalContainer.mock'; import { act, fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Modal } from '@/components/Modal'; @@ -52,6 +53,22 @@ describe('Modal', () => { expect(screen.queryByTestId('modal-cancel-button')).not.toBeInTheDocument(); }); + describe('accessibility', () => { + test.each([ + ['renders null', { isOpen: false }], + ['renders Modal component', {}], + ['renders Modal with custom test id', { 'data-testid': 'custom-modal-id' }], + ['renders submit button when not hidden', { submitButtonHidden: false, submitButtonLabel: 'Submit' }], + ['renders Modal component without a cancel button', { cancelButtonHidden: true }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); + describe('event handlers', () => { beforeEach(() => { render(); diff --git a/src/test/__tests__/components/Pagination.test.tsx b/src/test/__tests__/components/Pagination.test.tsx index 1f1fe50ed..6fcf7590f 100644 --- a/src/test/__tests__/components/Pagination.test.tsx +++ b/src/test/__tests__/components/Pagination.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Pagination } from '@/components/Pagination'; @@ -15,6 +16,19 @@ describe('Pagination', () => { expect(getByTestId('pagination')).toBeInTheDocument(); }); + describe('accessibility', () => { + test.each([ + ['first page', { currentPage: 0, totalPages: 2 }], + ['last page', { currentPage: 1, totalPages: 2 }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); + describe('counts', () => { type PaginationProps = { currentPage: number; diff --git a/src/test/__tests__/components/SimpleLookupField.test.tsx b/src/test/__tests__/components/SimpleLookupField.test.tsx index 4dc6b8f30..ba7b67d2c 100644 --- a/src/test/__tests__/components/SimpleLookupField.test.tsx +++ b/src/test/__tests__/components/SimpleLookupField.test.tsx @@ -1,6 +1,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { axe } from 'jest-axe'; import { SimpleLookupField } from '@/components/SimpleLookupField'; @@ -80,4 +81,17 @@ describe('Simple lookup field', () => { expect(await findByText('value-3')).toBeInTheDocument(); }); }); + + describe('accessibility', () => { + test.each([ + ['multi select', true], + ['single select', false], + ])('has no accessibility violations when %s', async (_description, isMulti) => { + const { container } = renderScreen(isMulti); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/Table/HeaderCell.test.tsx b/src/test/__tests__/components/Table/HeaderCell.test.tsx index 15801b292..431f17021 100644 --- a/src/test/__tests__/components/Table/HeaderCell.test.tsx +++ b/src/test/__tests__/components/Table/HeaderCell.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { HeaderCell } from '@/components/Table/HeaderCell'; @@ -101,4 +102,21 @@ describe('HeaderCell', () => { expect(() => expectClickBehavior()).not.toThrow(); }); }); + + describe('accessibility', () => { + test.each([ + ['default', {}], + ['td element', { elementType: 'td' as const }], + ['JSX label', { label: JSX Label }], + ['undefined label', { label: undefined }], + ['clickable', { onHeaderCellClick: () => {} }], + ['custom className', { className: 'custom-class' }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = renderHeaderCell(overrides); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/Table/Table.test.tsx b/src/test/__tests__/components/Table/Table.test.tsx index 4e3e4e1ea..7dc328e96 100644 --- a/src/test/__tests__/components/Table/Table.test.tsx +++ b/src/test/__tests__/components/Table/Table.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Table } from '@/components/Table'; @@ -30,9 +31,13 @@ describe('Table', () => { const onHeaderCellClick = jest.fn(); const onRowClick = jest.fn(); - beforeEach(() => - render(), - ); + let container: HTMLElement; + + beforeEach(() => { + ({ container } = render( +
, + )); + }); it('renders Table', () => { expect(screen.getByTestId('table')).toBeInTheDocument(); @@ -61,4 +66,12 @@ describe('Table', () => { it('renders empty cell if no corresponding entry exists in data arg', () => { expect(screen.getByTestId(mockHeader.nonExistent.label)).toBeEmptyDOMElement(); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/TableFlex.test.tsx b/src/test/__tests__/components/TableFlex.test.tsx index c3a180b28..82ff01782 100644 --- a/src/test/__tests__/components/TableFlex.test.tsx +++ b/src/test/__tests__/components/TableFlex.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { TableFlex } from '@/components/Table/TableFlex'; @@ -16,8 +17,12 @@ describe('TableFlex Component', () => { const onRowClick = jest.fn(); const onHeaderCellClick = jest.fn(); + let container: HTMLElement; + beforeEach(() => { - render(); + ({ container } = render( + , + )); }); test('renders TableFlex component', () => { @@ -56,4 +61,12 @@ describe('TableFlex Component', () => { expect(onRowClick).toHaveBeenCalledWith(data[0]); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/components/Tooltip.test.tsx b/src/test/__tests__/components/Tooltip.test.tsx index d9b303c9b..6b535b5e7 100644 --- a/src/test/__tests__/components/Tooltip.test.tsx +++ b/src/test/__tests__/components/Tooltip.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Tooltip } from '@/components/Tooltip'; @@ -126,4 +127,26 @@ describe('Tooltip', () => { fireEvent.click(button); expect(screen.getByTestId('tooltip-trigger__content')).toHaveClass('custom-content'); }); + + describe('accessibility', () => { + test.each([ + ['default', {}], + ['with custom class names', { className: 'custom-trigger', contentClassName: 'custom-content' }], + ])('has no accessibility violations when %s', async (_description, overrides) => { + const { container } = render( + , + ); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/views/ExternalResourcePreview.test.tsx b/src/test/__tests__/views/ExternalResourcePreview.test.tsx index ad74abe59..f7bd3e957 100644 --- a/src/test/__tests__/views/ExternalResourcePreview.test.tsx +++ b/src/test/__tests__/views/ExternalResourcePreview.test.tsx @@ -1,6 +1,7 @@ import { MemoryRouter } from 'react-router-dom'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { ExternalResourcePreview } from '@/views'; @@ -42,4 +43,19 @@ describe('ExternalResourcePreview', () => { expect(screen.getByTestId('preview-fields')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['loading', { data: null, isLoading: true }], + ['loaded', { data: { schema: new Map(), userValues: {}, initKey: 'key' }, isLoading: false }], + ])('has no accessibility violations when %s', async (_description, mockReturn) => { + mockUseResourcePreviewQuery.mockReturnValue(mockReturn); + + const { container } = renderComponent(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/views/Root.test.tsx b/src/test/__tests__/views/Root.test.tsx index 7b337ae7c..31c90ccc5 100644 --- a/src/test/__tests__/views/Root.test.tsx +++ b/src/test/__tests__/views/Root.test.tsx @@ -8,6 +8,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Root } from '@/views'; @@ -26,7 +27,7 @@ describe('Root', () => { }, ]); - render( + return render( } /> @@ -49,4 +50,17 @@ describe('Root', () => { expect(getByTestId('loading-component')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['default', false], + ['loading', true], + ])('has no accessibility violations when %s', async (_description, isLoading) => { + const { container } = renderRootComponent(isLoading); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/__tests__/views/Search.test.tsx b/src/test/__tests__/views/Search.test.tsx index c1e178419..d0eee527e 100644 --- a/src/test/__tests__/views/Search.test.tsx +++ b/src/test/__tests__/views/Search.test.tsx @@ -6,6 +6,7 @@ import { BrowserRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { TYPE_URIS } from '@/common/constants/bibframe.constants'; import * as BuildConstants from '@/common/constants/build.constants'; @@ -52,9 +53,11 @@ const renderWithProviders = (component: React.ReactNode) => { }; describe('Search', () => { + let container: HTMLElement; + beforeEach(() => { queryClient.clear(); - renderWithProviders(); + ({ container } = renderWithProviders()); }); test('renders Search component', () => { @@ -124,4 +127,12 @@ describe('Search', () => { expect(navigateToManageProfileSettings).toHaveBeenCalled(); }); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/test/setupTests.ts b/src/test/setupTests.ts index fc7d7dca9..bb52f648b 100644 --- a/src/test/setupTests.ts +++ b/src/test/setupTests.ts @@ -1,4 +1,5 @@ import '@testing-library/jest-dom'; +import 'jest-axe/extend-expect'; import { enableFetchMocks } from 'jest-fetch-mock'; enableFetchMocks(); diff --git a/src/views/Edit/Edit.test.tsx b/src/views/Edit/Edit.test.tsx index 3f2d99f4f..da96df770 100644 --- a/src/views/Edit/Edit.test.tsx +++ b/src/views/Edit/Edit.test.tsx @@ -11,6 +11,7 @@ import { setInitialGlobalState, setUpdatedGlobalState } from '@/test/__mocks__/s import * as Router from 'react-router-dom'; import { act, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import * as BibframeConstants from '@/common/constants/bibframe.constants'; import { Edit } from '@/views'; @@ -127,4 +128,16 @@ describe('Edit', () => { expect(applyUpdatedSettingsToResource).toHaveBeenCalled(); }); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + jest.spyOn(Router, 'useParams').mockReturnValue({ resourceId: 'testResourceId' }); + + const { container } = await renderComponent(monograph as unknown as ProfileEntry); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/views/ExternalResource/components/PreviewExternalResourceControls/PreviewExternalResourceControls.test.tsx b/src/views/ExternalResource/components/PreviewExternalResourceControls/PreviewExternalResourceControls.test.tsx index 96a1c16e8..8d91bba35 100644 --- a/src/views/ExternalResource/components/PreviewExternalResourceControls/PreviewExternalResourceControls.test.tsx +++ b/src/views/ExternalResource/components/PreviewExternalResourceControls/PreviewExternalResourceControls.test.tsx @@ -2,6 +2,7 @@ import * as ReactRouterDom from 'react-router-dom'; import { fireEvent, screen, waitFor } from '@testing-library/dom'; import { act, render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { PreviewExternalResourceControls } from '@/views/ExternalResource/components/PreviewExternalResourceControls'; @@ -58,4 +59,14 @@ describe('PreviewExternalResourceControls', () => { expect(navigate).toHaveBeenCalled(); }); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const { container } = renderComponent(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/views/ExternalResource/components/PreviewExternalResourcePane/PreviewExternalResourcePane.test.tsx b/src/views/ExternalResource/components/PreviewExternalResourcePane/PreviewExternalResourcePane.test.tsx index 0cce3a023..fb9bdcf06 100644 --- a/src/views/ExternalResource/components/PreviewExternalResourcePane/PreviewExternalResourcePane.test.tsx +++ b/src/views/ExternalResource/components/PreviewExternalResourcePane/PreviewExternalResourcePane.test.tsx @@ -1,5 +1,6 @@ import { fireEvent, screen } from '@testing-library/dom'; import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { PreviewExternalResourcePane } from '@/views/ExternalResource/components/PreviewExternalResourcePane'; @@ -51,4 +52,19 @@ describe('PreviewExternalResourcePane', () => { expect(navigate).toHaveBeenCalled(); }); + + describe('accessibility', () => { + test.each([ + ['query data is available', { data: { record: mockRecord } }], + ['query has no data', { data: null }], + ])('has no accessibility violations when %s', async (_description, queryResult) => { + mockUseResourcePreviewQuery.mockReturnValue(queryResult); + + const { container } = render(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/views/ManageProfileSettings/ManageProfileSettings.test.tsx b/src/views/ManageProfileSettings/ManageProfileSettings.test.tsx index 42bf5db45..b28664d86 100644 --- a/src/views/ManageProfileSettings/ManageProfileSettings.test.tsx +++ b/src/views/ManageProfileSettings/ManageProfileSettings.test.tsx @@ -3,6 +3,7 @@ import { BrowserRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { fetchPreferredProfiles, fetchProfile, fetchProfileSettings, fetchProfiles } from '@/common/api/profiles.api'; import { BFLITE_URIS } from '@/common/constants/bibframeMapping.constants'; @@ -85,12 +86,14 @@ describe('ManageProfileSettings', () => { children: [], }; + let container: HTMLElement; + beforeEach(() => { (fetchProfiles as jest.Mock).mockResolvedValue(mockProfiles); (fetchPreferredProfiles as jest.Mock).mockResolvedValue(mockPreferredProfiles); (fetchProfile as jest.Mock).mockResolvedValue(mockProfile); (fetchProfileSettings as jest.Mock).mockResolvedValue(mockProfileSettings); - renderComponent(); + ({ container } = renderComponent()); }); afterEach(() => { @@ -387,4 +390,12 @@ describe('ManageProfileSettings', () => { }); }); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/views/Root/components/CommonStatus.test.tsx b/src/views/Root/components/CommonStatus.test.tsx index 832f9dc3a..a5f6ac426 100644 --- a/src/views/Root/components/CommonStatus.test.tsx +++ b/src/views/Root/components/CommonStatus.test.tsx @@ -3,6 +3,7 @@ import { setInitialGlobalState } from '@/test/__mocks__/store'; import { MemoryRouter } from 'react-router-dom'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { StatusType } from '@/common/constants/status.constants'; @@ -120,4 +121,24 @@ describe('CommonStatus', () => { expect(getStatusMessagesCount(container)).toBe(1); expect(screen.getByText('ld.rdUpdateSuccess')).toBeInTheDocument(); }); + + describe('accessibility', () => { + test.each([ + ['no status messages', []], + [ + 'status messages present', + [ + { id: '01', type: StatusType.success, message: 'test message 1' }, + { id: '02', type: StatusType.error, message: 'test message 2' }, + { id: '03', type: StatusType.warning, message: 'test message 3' }, + ], + ], + ])('has no accessibility violations when %s', async (_description, statusMessages) => { + const { container } = renderComponent(statusMessages); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/views/Root/components/Footer.test.tsx b/src/views/Root/components/Footer.test.tsx index b5b226245..4107eb4bc 100644 --- a/src/views/Root/components/Footer.test.tsx +++ b/src/views/Root/components/Footer.test.tsx @@ -1,6 +1,7 @@ import { MemoryRouter } from 'react-router-dom'; import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Footer } from './Footer'; @@ -106,4 +107,34 @@ describe('Footer', () => { expect(screen.getByTestId('record-controls')).toBeInTheDocument(); }); }); + + describe('accessibility', () => { + test.each([ + ['external resource route with no data', () => setActiveRoute(2)], + [ + 'external resource route with data', + () => { + setActiveRoute(2); + mockUseResourcePreviewQuery.mockReturnValue({ data: { record: {} } }); + }, + ], + ['hub import route with no data', () => setActiveRoute(3)], + [ + 'hub import route with data', + () => { + setActiveRoute(3); + mockUseHubQuery.mockReturnValue({ data: { resource: {} } }); + }, + ], + ['edit route', () => setActiveRoute(1)], + ])('has no accessibility violations when %s', async (_description, setup) => { + setup(); + + const { container } = renderFooter(); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/views/Search/components/FullDisplay/FullDisplay.test.tsx b/src/views/Search/components/FullDisplay/FullDisplay.test.tsx index d7e8fa93a..1cb01b098 100644 --- a/src/views/Search/components/FullDisplay/FullDisplay.test.tsx +++ b/src/views/Search/components/FullDisplay/FullDisplay.test.tsx @@ -6,6 +6,7 @@ import { Fragment, ReactNode } from 'react'; import { RouterProvider, createMemoryRouter } from 'react-router-dom'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { axe } from 'jest-axe'; import { Edit } from '@/views'; import { FullDisplay } from '@/views/Search/components/FullDisplay'; @@ -39,6 +40,8 @@ jest.mock('react-intl', () => ({ })); describe('FullDisplay', () => { + let container: HTMLElement; + beforeEach(() => { mockUseResourcePreviewQuery.mockImplementation((id: string) => ({ data: @@ -77,7 +80,7 @@ describe('FullDisplay', () => { }, ]; - return render(); + ({ container } = render()); }); const { getByTestId, getAllByTestId } = screen; @@ -99,4 +102,12 @@ describe('FullDisplay', () => { expect(screen.getByTestId('edit-page')).toBeInTheDocument(); }); }); + + describe('accessibility', () => { + test('has no accessibility violations', async () => { + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); diff --git a/src/views/Search/components/ModalImport/ModalImport.test.tsx b/src/views/Search/components/ModalImport/ModalImport.test.tsx index 79236d757..35545dadc 100644 --- a/src/views/Search/components/ModalImport/ModalImport.test.tsx +++ b/src/views/Search/components/ModalImport/ModalImport.test.tsx @@ -6,6 +6,7 @@ import { BrowserRouter } from 'react-router-dom'; import { fireEvent, render, screen } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; +import { axe } from 'jest-axe'; import * as importApi from '@/common/api/import.api'; import { ImportFilterTypes, ImportModes } from '@/common/constants/import.constants'; @@ -57,7 +58,7 @@ describe('ModalImport', () => { }, }, ]); - render( + return render( , @@ -359,4 +360,18 @@ describe('ModalImport', () => { expect(spy).toHaveBeenCalled(); }); }); + + describe('accessibility', () => { + test.each([ + ['default import window', undefined], + ['hub import', ImportFilterTypes.Hub], + ['instance import', ImportFilterTypes.Instance], + ])('has no accessibility violations when %s', async (_description, filterType) => { + const { container } = renderComponent(filterType); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + }); }); From 2004e2703c83d415fcaaea6f5c16e69ae64dd4e2 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Fri, 24 Jul 2026 23:12:47 +0000 Subject: [PATCH 2/5] Adjust tests to provide any needed label context for accessibility checks. Remove tests on wrappers. Remove unnecessary format / input mocks interfering with tests. Add aria-labelledby to Dropzone input. Comment out tests that need fixes and note which story should fix them. --- CHANGELOG.md | 2 + package-lock.json | 196 +++++++++++++++++- src/components/Dropzone/Drop.tsx | 4 +- .../ComplexLookupField.test.tsx | 16 +- .../EditSection/EditSection.test.tsx | 23 +- .../ProfileSettingsSelector.test.tsx | 24 ++- .../BaseComponent.test.tsx | 6 +- .../ProfileSettingsEditor.test.tsx | 6 +- .../SearchControls/InputsWrapper.test.tsx | 14 +- .../InstanceSelectCtlFormatter.test.tsx | 14 +- .../__tests__/components/Accordion.test.tsx | 2 +- .../__tests__/components/DatePicker.test.tsx | 3 +- .../components/DropdownField.test.tsx | 7 +- .../__tests__/components/Dropzone.test.tsx | 12 +- .../components/LiteralField.test.tsx | 8 +- .../components/SimpleLookupField.test.tsx | 10 +- .../ManageProfileSettings.test.tsx | 17 +- .../Root/components/CommonStatus.test.tsx | 5 +- src/views/Root/components/Nav.test.tsx | 3 + .../FullDisplay/FullDisplay.test.tsx | 16 -- 20 files changed, 319 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 044f9f6dd..09fbefb17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * Fix field collision in the record generation. Fixes [UILD-838]. * Update language used for importing works/instances. Refs [UILD-839]. * Fix multiple settings per profile. Refs [UILD-780]. +* Add accessibility tests. Refs [UILD-841]. [UILD-744]:https://folio-org.atlassian.net/browse/UILD-744 [UILD-816]:https://folio-org.atlassian.net/browse/UILD-816 @@ -23,6 +24,7 @@ [UILD-838]:https://folio-org.atlassian.net/browse/UILD-838 [UILD-839]:https://folio-org.atlassian.net/browse/UILD-839 [UILD-780]:https://folio-org.atlassian.net/browse/UILD-780 +[UILD-841]:https://folio-org.atlassian.net/browse/UILD-841 ## 2.0.4 (2026-06-03) * Fix default profile type persistence across edit form and profile settings. Fixes [UILD-820]. diff --git a/package-lock.json b/package-lock.json index 5750244b0..d1c605f58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "@testing-library/user-event": "^14.6.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/jest": "^30.0.0", + "@types/jest-axe": "^3.5.9", "@types/lodash": "^4.17.14", "@types/node": "^26.1.1", "@types/react": "^19.2.2", @@ -57,6 +58,7 @@ "husky": "^9.1.7", "identity-obj-proxy": "^3.0.0", "jest": "^30.3.0", + "jest-axe": "^10.0.0", "jest-environment-jsdom": "^30.3.0", "jest-fetch-mock": "^3.0.3", "lint-staged": "^16.4.0", @@ -837,10 +839,33 @@ "react": ">=16.8.0" } }, + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, @@ -2419,6 +2444,17 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", @@ -3161,6 +3197,17 @@ "pretty-format": "^30.0.0" } }, + "node_modules/@types/jest-axe": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@types/jest-axe/-/jest-axe-3.5.9.tgz", + "integrity": "sha512-z98CzR0yVDalCEuhGXXO4/zN4HHuSebAukXDjTLJyjEAgoUf1H1i+sr7SUB/mz8CRS/03/XChsx0dcLjHkndoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jest": "*", + "axe-core": "^3.5.5" + } + }, "node_modules/@types/jest/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -4239,6 +4286,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axe-core": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.6.tgz", + "integrity": "sha512-LEUDjgmdJoA3LqklSTwKYqkjcZ4HKc4ddIYGSAiSkr46NTjzg2L9RNB+lekO9P7Dlpa87+hBtzc2Fzn/+GUWMQ==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, "node_modules/babel-jest": { "version": "30.3.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", @@ -5137,6 +5194,16 @@ "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -7492,6 +7559,119 @@ } } }, + "node_modules/jest-axe": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jest-axe/-/jest-axe-10.0.0.tgz", + "integrity": "sha512-9QR0M7//o5UVRnEUUm68IsGapHrcKGakYy9dKWWMX79LmeUKguDI6DREyljC5I13j78OUmtKLF5My6ccffLFBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "axe-core": "4.10.2", + "chalk": "4.1.2", + "jest-matcher-utils": "29.2.2", + "lodash.merge": "4.6.2" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/jest-axe/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-axe/node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-axe/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-axe/node_modules/axe-core": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-axe/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-axe/node_modules/jest-matcher-utils": { + "version": "29.2.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", + "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.2.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.2.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-axe/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-axe/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-changed-files": { "version": "30.3.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", @@ -7862,6 +8042,16 @@ "promise-polyfill": "^8.1.3" } }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-haste-map": { "version": "30.3.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", diff --git a/src/components/Dropzone/Drop.tsx b/src/components/Dropzone/Drop.tsx index eb5c898cb..37b5ebc9a 100644 --- a/src/components/Dropzone/Drop.tsx +++ b/src/components/Dropzone/Drop.tsx @@ -19,13 +19,13 @@ export const Drop: FC = ({ getRootProps, getInputProps, isDragActive }) = {...getRootProps()} data-testid="dropzone" > - + {isDragActive ? (
) : ( -
+