diff --git a/packages/ui-modal/src/Modal/__tests__/ModalBody.test.tsx b/packages/ui-modal/src/Modal/__tests__/ModalBody.test.tsx index e3d043efee..dda44e9613 100644 --- a/packages/ui-modal/src/Modal/__tests__/ModalBody.test.tsx +++ b/packages/ui-modal/src/Modal/__tests__/ModalBody.test.tsx @@ -31,7 +31,7 @@ import canvas from '@instructure/ui-themes' import { View } from '@instructure/ui-view/latest' import type { ViewOwnProps } from '@instructure/ui-view/latest' -import { ModalBody } from '@instructure/ui-modal/latest' +import { Modal, ModalBody, ModalHeader } from '@instructure/ui-modal/latest' const BODY_TEXT = 'Modal-body-text' @@ -209,5 +209,36 @@ describe('', () => { await waitFor(() => expect(body).not.toHaveAttribute('tabindex')) }) + + it('labels the scrollable body from the header with a space between adjacent text nodes', async () => { + mockScrollable(true) + // The header reproduces what a `Heading` with `aiVariant="stacked"` + // renders: a decorative "IgniteAI" text node immediately followed by the + // title, with no whitespace between them. The body's aria-label must not + // collapse these into "IgniteAI Nutrition Facts". + const { findByText } = render( + {}} + > + + + {'AI Nutrition Facts'} + + {BODY_TEXT} + + ) + const body = await findByText(BODY_TEXT) + + await waitFor(() => + expect(body).toHaveAttribute( + 'aria-label', + 'IgniteAI AI Nutrition Facts' + ) + ) + }) }) }) diff --git a/packages/ui-modal/src/Modal/v2/ModalHeader/index.tsx b/packages/ui-modal/src/Modal/v2/ModalHeader/index.tsx index 13403c8f6e..42820d997d 100644 --- a/packages/ui-modal/src/Modal/v2/ModalHeader/index.tsx +++ b/packages/ui-modal/src/Modal/v2/ModalHeader/index.tsx @@ -75,12 +75,19 @@ class ModalHeader extends Component { : NodeFilter.FILTER_ACCEPT } }) - let text = '' + const textParts: string[] = [] let current while ((current = walker.nextNode())) { - text += current.nodeValue + // Trim each text node and drop empty ones so adjacent text nodes from + // different elements (e.g. the decorative "IgniteAI" span a `Heading` + // with `aiVariant` renders before its title) are separated by a single + // space instead of being concatenated into "IgniteAI Nutrition Facts". + const value = current.nodeValue?.trim() + if (value) { + textParts.push(value) + } } - return text + return textParts.join(' ') } handleRef = (el: HTMLDivElement | null) => {