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(
+ {}}
+ >
+
+ IgniteAI
+ {'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) => {