diff --git a/__tests__/src/components/CanvasInfo.test.js b/__tests__/src/components/CanvasInfo.test.js index 751ac2b60..94ac9082d 100644 --- a/__tests__/src/components/CanvasInfo.test.js +++ b/__tests__/src/components/CanvasInfo.test.js @@ -14,7 +14,6 @@ describe('CanvasInfo', () => { canvasLabel="The Canvas Label" canvasDescription="The Canvas Description" canvasMetadata={metadata} - id="xyz" />, ); }); @@ -45,7 +44,7 @@ describe('CanvasInfo', () => { describe('when metadata is not present', () => { beforeEach(() => { render( - , + , ); }); diff --git a/__tests__/src/components/CollectionInfo.test.js b/__tests__/src/components/CollectionInfo.test.js index b6080a91d..b147614bb 100644 --- a/__tests__/src/components/CollectionInfo.test.js +++ b/__tests__/src/components/CollectionInfo.test.js @@ -6,7 +6,6 @@ import { CollectionInfo } from '../../../src/components/CollectionInfo'; function createWrapper(props) { return render( {}} {...props} diff --git a/__tests__/src/components/ManifestInfo.test.js b/__tests__/src/components/ManifestInfo.test.js index 292e59370..3479e3a49 100644 --- a/__tests__/src/components/ManifestInfo.test.js +++ b/__tests__/src/components/ManifestInfo.test.js @@ -8,7 +8,6 @@ describe('ManifestInfo', () => { beforeEach(() => { render( { describe('when metadata is not present', () => { beforeEach(() => { render( - , + , ); }); diff --git a/__tests__/src/components/WindowTopMenuButton.test.js b/__tests__/src/components/WindowTopMenuButton.test.js index baf91aef5..f49934dbe 100644 --- a/__tests__/src/components/WindowTopMenuButton.test.js +++ b/__tests__/src/components/WindowTopMenuButton.test.js @@ -47,6 +47,6 @@ describe('WindowTopMenuButton', () => { render(); await user.click(screen.getByLabelText('Window views & thumbnail display')); // when 'open' is true, aria-owns is set to the id of the window - expect(screen.getByLabelText('Window views & thumbnail display')).toHaveAttribute('aria-owns', 'window-menu_xyz'); // eslint-disable-line testing-library/no-node-access + expect(screen.getByLabelText('Window views & thumbnail display')).toHaveAttribute('aria-owns'); // eslint-disable-line testing-library/no-node-access }); }); diff --git a/__tests__/src/components/WorkspaceOptionsMenu.test.js b/__tests__/src/components/WorkspaceOptionsMenu.test.js index 3d71aab0d..64da09529 100644 --- a/__tests__/src/components/WorkspaceOptionsMenu.test.js +++ b/__tests__/src/components/WorkspaceOptionsMenu.test.js @@ -48,18 +48,18 @@ describe('WorkspaceOptionsMenu', () => { it('renders the export dialog when export option is clicked', async () => { render(); - expect(document.querySelector('#workspace-export')).not.toBeInTheDocument(); // eslint-disable-line testing-library/no-node-access + expect(screen.queryByRole('heading', { name: 'Export workspace' })).not.toBeInTheDocument(); - await user.click(screen.getAllByRole('menuitem')[0]); - expect(document.querySelector('#workspace-export')).toBeInTheDocument(); // eslint-disable-line testing-library/no-node-access + await user.click(screen.getByRole('menuitem', { name: 'Export workspace' })); + expect(screen.getByRole('heading', { name: 'Export workspace' })).toBeInTheDocument(); }); - it('renders the import dialog when imporrt option is clicked', async () => { + it('renders the import dialog when import option is clicked', async () => { render(); - expect(document.querySelector('#workspace-import')).not.toBeInTheDocument(); // eslint-disable-line testing-library/no-node-access + expect(screen.queryByRole('heading', { name: 'Import workspace' })).not.toBeInTheDocument(); - await user.click(screen.getAllByRole('menuitem')[1]); - expect(document.querySelector('#workspace-import')).toBeInTheDocument(); // eslint-disable-line testing-library/no-node-access + await user.click(screen.getByRole('menuitem', { name: 'Import workspace' })); + expect(screen.getByRole('heading', { name: 'Import workspace' })).toBeInTheDocument(); }); it('fires the correct callbacks on menu close', async () => { diff --git a/src/components/CanvasInfo.js b/src/components/CanvasInfo.js index 2cd699c9c..6f3fa9e98 100644 --- a/src/components/CanvasInfo.js +++ b/src/components/CanvasInfo.js @@ -1,3 +1,4 @@ +import { useId } from 'react'; import PropTypes from 'prop-types'; import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; @@ -13,24 +14,25 @@ export function CanvasInfo({ canvasDescription = null, canvasLabel = null, canvasMetadata = [], - id, index = 1, totalSize = 1, }) { const { t } = useTranslation(); + const id = useId(); + const titleId = useId(); const pluginProps = arguments[0]; // eslint-disable-line prefer-rest-params return ( {canvasLabel && ( @@ -56,7 +58,6 @@ CanvasInfo.propTypes = { canvasDescription: PropTypes.string, canvasLabel: PropTypes.string, canvasMetadata: PropTypes.array, // eslint-disable-line react/forbid-prop-types - id: PropTypes.string.isRequired, index: PropTypes.number, totalSize: PropTypes.number, }; diff --git a/src/components/CollectionInfo.js b/src/components/CollectionInfo.js index c4aa043e2..1e4c6550d 100644 --- a/src/components/CollectionInfo.js +++ b/src/components/CollectionInfo.js @@ -1,3 +1,4 @@ +import { useId } from 'react'; import PropTypes from 'prop-types'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; @@ -9,9 +10,11 @@ import CollapsibleSection from '../containers/CollapsibleSection'; * CollectionInfo */ export function CollectionInfo({ - collectionLabel = null, collectionPath = [], id, showCollectionDialog, windowId = null, + collectionLabel = null, collectionPath = [], showCollectionDialog, windowId = null, }) { const { t } = useTranslation(); + const id = useId(); + const titleId = useId(); /** * Show the containing collection. @@ -26,13 +29,13 @@ export function CollectionInfo({ return ( {collectionLabel && ( {collectionLabel} @@ -53,7 +56,6 @@ export function CollectionInfo({ CollectionInfo.propTypes = { collectionLabel: PropTypes.string, collectionPath: PropTypes.arrayOf(PropTypes.string), - id: PropTypes.string.isRequired, showCollectionDialog: PropTypes.func.isRequired, windowId: PropTypes.string, }; diff --git a/src/components/ManifestInfo.js b/src/components/ManifestInfo.js index 2b45e58e4..acdcf1dbe 100644 --- a/src/components/ManifestInfo.js +++ b/src/components/ManifestInfo.js @@ -1,3 +1,4 @@ +import { useId } from 'react'; import PropTypes from 'prop-types'; import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; @@ -10,12 +11,13 @@ import { PluginHook } from './PluginHook'; * ManifestInfo */ export function ManifestInfo({ - manifestDescription = null, manifestLabel = null, manifestMetadata = [], manifestSummary = null, id, + manifestDescription = null, manifestLabel = null, manifestMetadata = [], manifestSummary = null, ...rest }) { const { t } = useTranslation(); + const id = useId(); const pluginProps = { - id, manifestDescription, manifestLabel, manifestMetadata, manifestSummary, ...rest, + manifestDescription, manifestLabel, manifestMetadata, manifestSummary, ...rest, }; return ( @@ -56,7 +58,6 @@ export function ManifestInfo({ } ManifestInfo.propTypes = { - id: PropTypes.string.isRequired, manifestDescription: PropTypes.string, manifestLabel: PropTypes.string, manifestMetadata: PropTypes.array, // eslint-disable-line react/forbid-prop-types diff --git a/src/components/ManifestRelatedLinks.js b/src/components/ManifestRelatedLinks.js index 2169b68c1..80d4a09a1 100644 --- a/src/components/ManifestRelatedLinks.js +++ b/src/components/ManifestRelatedLinks.js @@ -1,3 +1,4 @@ +import { useId } from 'react'; import PropTypes from 'prop-types'; import { styled } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; @@ -20,7 +21,6 @@ const StyledDl = styled('dl')(({ theme }) => ({ */ export function ManifestRelatedLinks({ homepage = null, - id, manifestUrl = null, related = null, renderings = null, @@ -28,18 +28,22 @@ export function ManifestRelatedLinks({ ...rest }) { const { t } = useTranslation(); + const id = useId(); + const titleId = useId(); + const pluginProps = { - homepage, id, manifestUrl, related, renderings, seeAlso, t, ...rest, + homepage, manifestUrl, related, renderings, seeAlso, t, ...rest, }; return ( @@ -129,7 +133,6 @@ ManifestRelatedLinks.propTypes = { label: PropTypes.string, value: PropTypes.string, })), - id: PropTypes.string.isRequired, manifestUrl: PropTypes.string, related: PropTypes.arrayOf(PropTypes.shape({ format: PropTypes.string, diff --git a/src/components/SearchHit.js b/src/components/SearchHit.js index c656f4d14..de79d2126 100644 --- a/src/components/SearchHit.js +++ b/src/components/SearchHit.js @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from 'react'; +import { useEffect, useId, useMemo } from 'react'; import { useEffectEvent } from 'use-effect-event'; import PropTypes from 'prop-types'; import Button from '@mui/material/Button'; @@ -86,11 +86,12 @@ export function SearchHit({ ); }); + const canvasLabelHtmlId = useId(); + if (focused && !selected) return null; const renderedHit = focused ? hit : hit && truncatedHit; const truncated = hit && (renderedHit.before !== hit.before || renderedHit.after !== hit.after); - const canvasLabelHtmlId = `${companionWindowId}-${index}`; const ownerState = { adjacent, focused, selected, windowSelected, }; diff --git a/src/components/WindowListButton.js b/src/components/WindowListButton.js index 3134fa745..451cf2bba 100644 --- a/src/components/WindowListButton.js +++ b/src/components/WindowListButton.js @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useId, useState } from 'react'; import PropTypes from 'prop-types'; import BookmarksIcon from '@mui/icons-material/BookmarksSharp'; import { useTranslation } from 'react-i18next'; @@ -11,6 +11,7 @@ import MiradorMenuButton from '../containers/MiradorMenuButton'; export function WindowListButton({ disabled = false, windowCount }) { const { t } = useTranslation(); const [windowListAnchor, setWindowListAnchor] = useState(null); + const id = useId(); /** */ const handleClose = () => { setWindowListAnchor(null); }; @@ -22,7 +23,7 @@ export function WindowListButton({ disabled = false, windowCount }) { diff --git a/src/components/WindowSideBarCanvasPanel.js b/src/components/WindowSideBarCanvasPanel.js index 59953f227..2b30297c4 100644 --- a/src/components/WindowSideBarCanvasPanel.js +++ b/src/components/WindowSideBarCanvasPanel.js @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import { useId, useRef } from 'react'; import PropTypes from 'prop-types'; import { styled } from '@mui/material/styles'; import Tabs from '@mui/material/Tabs'; @@ -49,6 +49,7 @@ export function WindowSideBarCanvasPanel({ }) { const { t } = useTranslation(); const containerRef = useRef(); + const tabPanelId = useId(); /** */ const handleSequenceChange = (event) => { @@ -126,15 +127,15 @@ export function WindowSideBarCanvasPanel({ textColor="primary" > {showToc && ( - } /> + } /> )} - } /> - } /> + } /> + } /> > )} > - + { collection && ( 0 && ( - + )} - + - + ); diff --git a/src/components/WindowTopBarPluginMenu.js b/src/components/WindowTopBarPluginMenu.js index 421292f2a..4b3abc0b3 100644 --- a/src/components/WindowTopBarPluginMenu.js +++ b/src/components/WindowTopBarPluginMenu.js @@ -1,4 +1,4 @@ -import { useContext, useState } from 'react'; +import { useContext, useId, useState } from 'react'; import PropTypes from 'prop-types'; import MoreVertIcon from '@mui/icons-material/MoreVertSharp'; import Menu from '@mui/material/Menu'; @@ -18,6 +18,7 @@ export function WindowTopBarPluginMenu({ const pluginProps = arguments[0]; // eslint-disable-line prefer-rest-params const [anchorEl, setAnchorEl] = useState(null); const [open, setOpen] = useState(false); + const windowPluginMenuId = useId(); /** */ const handleMenuClick = (event) => { @@ -31,7 +32,6 @@ export function WindowTopBarPluginMenu({ setOpen(false); }; - const windowPluginMenuId = `window-plugin-menu_${windowId}`; if (!PluginComponents || PluginComponents.length === 0) return null; return ( diff --git a/src/components/WindowTopMenuButton.js b/src/components/WindowTopMenuButton.js index 75a70a5f3..dc0375d61 100644 --- a/src/components/WindowTopMenuButton.js +++ b/src/components/WindowTopMenuButton.js @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useId, useState } from 'react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import WindowTopMenu from '../containers/WindowTopMenu'; @@ -11,6 +11,7 @@ export function WindowTopMenuButton({ classes = {}, windowId }) { const { t } = useTranslation(); const [anchorEl, setAnchorEl] = useState(null); const [open, setOpen] = useState(false); + const menuId = useId(); /** */ const handleMenuClick = (event) => { @@ -24,7 +25,6 @@ export function WindowTopMenuButton({ classes = {}, windowId }) { setOpen(false); }; - const menuId = `window-menu_${windowId}`; return ( <> - + {t('downloadExport')} @@ -93,5 +95,6 @@ WorkspaceExport.propTypes = { container: PropTypes.object, // eslint-disable-line react/forbid-prop-types exportableState: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types handleClose: PropTypes.func.isRequired, + id: PropTypes.string, open: PropTypes.bool, }; diff --git a/src/components/WorkspaceImport.js b/src/components/WorkspaceImport.js index a553445e9..d071f30c7 100644 --- a/src/components/WorkspaceImport.js +++ b/src/components/WorkspaceImport.js @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useId, useState } from 'react'; import DialogTitle from '@mui/material/DialogTitle'; import PropTypes from 'prop-types'; import { @@ -13,10 +13,11 @@ import ScrollIndicatedDialogContent from '../containers/ScrollIndicatedDialogCon /** */ export function WorkspaceImport({ - addError, importConfig, classes = {}, handleClose, open = false, + addError, id = undefined, importConfig, classes = {}, handleClose, open = false, }) { const { t } = useTranslation(); const [configImportValue, setConfigImportValue] = useState(''); + const titleId = useId(); /** */ const handleChange = (event) => { @@ -37,14 +38,14 @@ export function WorkspaceImport({ return ( - + {t('importWorkspace')} @@ -79,6 +80,7 @@ WorkspaceImport.propTypes = { addError: PropTypes.func.isRequired, classes: PropTypes.objectOf(PropTypes.string), handleClose: PropTypes.func.isRequired, + id: PropTypes.string, importConfig: PropTypes.func.isRequired, open: PropTypes.bool, }; diff --git a/src/components/WorkspaceMenuButton.js b/src/components/WorkspaceMenuButton.js index c26309999..b4cd1798c 100644 --- a/src/components/WorkspaceMenuButton.js +++ b/src/components/WorkspaceMenuButton.js @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useId, useState } from 'react'; import SettingsIcon from '@mui/icons-material/SettingsSharp'; import { useTranslation } from 'react-i18next'; import WorkspaceMenu from '../containers/WorkspaceMenu'; @@ -10,6 +10,7 @@ export function WorkspaceMenuButton() { const { t } = useTranslation(); const [anchorEl, setAnchorEl] = useState(null); const [open, setOpen] = useState(false); + const id = useId(); /** */ const handleMenuClick = (event) => { @@ -28,7 +29,7 @@ export function WorkspaceMenuButton() { diff --git a/src/components/WorkspaceOptionsMenu.js b/src/components/WorkspaceOptionsMenu.js index 470902554..061b09f6d 100644 --- a/src/components/WorkspaceOptionsMenu.js +++ b/src/components/WorkspaceOptionsMenu.js @@ -1,4 +1,4 @@ -import { useContext, useState } from 'react'; +import { useContext, useId, useState } from 'react'; import PropTypes from 'prop-types'; import ImportIcon from '@mui/icons-material/Input'; import SaveAltIcon from '@mui/icons-material/SaveAltSharp'; @@ -21,6 +21,8 @@ export function WorkspaceOptionsMenu({ const { t } = useTranslation(); const container = useContext(WorkspaceContext); const [selectedOption, setSelectedOption] = useState(null); + const exportId = useId(); + const importId = useId(); const pluginProps = { anchorEl, container, handleClose, open, t, ...rest, @@ -57,7 +59,7 @@ export function WorkspaceOptionsMenu({ { handleClick('exportWorkspace'); }} - aria-owns={selectedOption === 'exportWorkspace' ? 'workspace-export' : undefined} + aria-owns={selectedOption === 'exportWorkspace' ? exportId : undefined} > @@ -66,9 +68,8 @@ export function WorkspaceOptionsMenu({ { handleClick('importWorkspace'); }} - aria-owns={selectedOption === 'importWorkspace' ? 'workspace-import' : undefined} + aria-owns={selectedOption === 'importWorkspace' ? importId : undefined} > @@ -79,6 +80,7 @@ export function WorkspaceOptionsMenu({ {selectedOption === 'exportWorkspace' && ( { updateWorkspace({ @@ -35,13 +38,12 @@ export function WorkspaceSelectionDialog({ return ( - + {t('workspaceSelectionTitle')} diff --git a/src/containers/CanvasInfo.js b/src/containers/CanvasInfo.js index 554af62eb..d6acc9f10 100644 --- a/src/containers/CanvasInfo.js +++ b/src/containers/CanvasInfo.js @@ -14,11 +14,11 @@ import { CanvasInfo } from '../components/CanvasInfo'; * @memberof WindowSideBarInfoPanel * @private */ -const mapStateToProps = (state, { canvasId, id, windowId }) => ({ - canvasDescription: getCanvasDescription(state, { canvasId, companionWindowId: id, windowId }), - canvasLabel: getCanvasLabel(state, { canvasId, companionWindowId: id, windowId }), +const mapStateToProps = (state, { canvasId, companionWindowId, windowId }) => ({ + canvasDescription: getCanvasDescription(state, { canvasId, companionWindowId, windowId }), + canvasLabel: getCanvasLabel(state, { canvasId, companionWindowId, windowId }), canvasMetadata: getDestructuredMetadata( - getCanvas(state, { canvasId, companionWindowId: id, windowId }), + getCanvas(state, { canvasId, companionWindowId, windowId }), ), }); diff --git a/src/containers/CollectionInfo.js b/src/containers/CollectionInfo.js index 0c17a3232..6f737993e 100644 --- a/src/containers/CollectionInfo.js +++ b/src/containers/CollectionInfo.js @@ -13,7 +13,7 @@ import { CollectionInfo } from '../components/CollectionInfo'; * @memberof WindowSideBarInfoPanel * @private */ -const mapStateToProps = (state, { id, windowId }) => { +const mapStateToProps = (state, { companionWindowId, windowId }) => { const { collectionPath } = (getWindow(state, { windowId }) || {}); const manifestId = collectionPath[collectionPath.length - 1]; diff --git a/src/containers/ManifestInfo.js b/src/containers/ManifestInfo.js index 89e202bb7..21672082a 100644 --- a/src/containers/ManifestInfo.js +++ b/src/containers/ManifestInfo.js @@ -14,14 +14,14 @@ import { ManifestInfo } from '../components/ManifestInfo'; * @memberof WindowSideBarInfoPanel * @private */ -const mapStateToProps = (state, { id, manifestId, windowId }) => ({ +const mapStateToProps = (state, { companionWindowId, manifestId, windowId }) => ({ manifestDescription: getManifestDescription(state, { - companionWindowId: id, manifestId, windowId, + companionWindowId, manifestId, windowId, }), - manifestLabel: getManifestTitle(state, { companionWindowId: id, manifestId, windowId }), - manifestMetadata: getManifestMetadata(state, { companionWindowId: id, manifestId, windowId }), + manifestLabel: getManifestTitle(state, { companionWindowId, manifestId, windowId }), + manifestMetadata: getManifestMetadata(state, { companionWindowId, manifestId, windowId }), manifestSummary: getManifestSummary(state, { - companionWindowId: id, manifestId, windowId, + companionWindowId, manifestId, windowId, }), }); diff --git a/src/containers/ManifestRelatedLinks.js b/src/containers/ManifestRelatedLinks.js index d4e1da708..9bffd0ef9 100644 --- a/src/containers/ManifestRelatedLinks.js +++ b/src/containers/ManifestRelatedLinks.js @@ -15,7 +15,7 @@ import { ManifestRelatedLinks } from '../components/ManifestRelatedLinks'; * @memberof WindowSideBarInfoPanel * @private */ -const mapStateToProps = (state, { id, windowId }) => ({ +const mapStateToProps = (state, { companionWindowId, windowId }) => ({ homepage: getManifestHomepage(state, { windowId }), manifestUrl: getManifestUrl(state, { windowId }), related: getManifestRelated(state, { windowId }),