From db56a89a8560d16db98bf0fb04aa68f4a37266eb Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Wed, 17 Jun 2026 14:15:28 +0900 Subject: [PATCH] Fix Alert explanation wrapper for rich children Signed-off-by: Arpit Jain --- src/components/Alert/alert.test.tsx | 35 +++++++++++++++++++++++++++-- src/components/Alert/alert.tsx | 20 +++++++++++++---- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/components/Alert/alert.test.tsx b/src/components/Alert/alert.test.tsx index 54856353ea..2ceb422279 100644 --- a/src/components/Alert/alert.test.tsx +++ b/src/components/Alert/alert.test.tsx @@ -53,6 +53,30 @@ describe('', () => { expect(explanation).toBeInTheDocument(); }); + it('renders text explanations in a paragraph', () => { + render( + + Explanation + , + ); + const explanation = screen.getByTestId('explanation'); + expect(explanation.tagName).toBe('P'); + expect(explanation).toHaveClass('m-notification__explanation'); + }); + + it('renders non-text explanations in a div to avoid invalid paragraph nesting', () => { + render( + +
    +
  • Resolve this issue
  • +
+
, + ); + const explanation = screen.getByTestId('explanation'); + expect(explanation.tagName).toBe('DIV'); + expect(within(explanation).getByRole('list')).toBeInTheDocument(); + }); + it('does not include an explanation wrapper class when there is no message but children are provided', () => { render( @@ -172,8 +196,15 @@ describe('', () => { }); it('renders field-level info alert without status modifier', () => { - render(); - const element = screen.getByTestId('message').parentElement; + const testId = 'field-level-info'; + render( + , + ); + const element = screen.getByTestId(testId); expect(element).toHaveClass('a-form-alert'); expect(element).not.toHaveClass('a-form-alert--info'); }); diff --git a/src/components/Alert/alert.tsx b/src/components/Alert/alert.tsx index d995b5b8e8..568dd5ee81 100644 --- a/src/components/Alert/alert.tsx +++ b/src/components/Alert/alert.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import type { HTMLAttributes, ReactNode } from 'react'; +import { Children, type HTMLAttributes, type ReactNode } from 'react'; import type { HeadingLevel } from '../../types/heading-level'; import type { JSXElement } from '../../types/jsx-element'; import { Icon } from '../Icon/icon'; @@ -20,6 +20,11 @@ export const iconByType: Record = { export type AlertType = 'error' | 'info' | 'loading' | 'success' | 'warning'; +const hasOnlyTextChildren = (children: ReactNode): boolean => + Children.toArray(children).every( + (child) => typeof child === 'string' || typeof child === 'number', + ); + interface AlertProperties { status?: AlertFieldLevelType | AlertType; message?: ReactNode; @@ -70,6 +75,13 @@ export const Alert = ({ className, ); + const explanationClassName = message + ? 'm-notification__explanation' + : undefined; + const explanationTag: 'div' | 'p' = + children && hasOnlyTextChildren(children) ? 'p' : 'div'; + const ExplanationTag = explanationTag; + return (
{showIcon ? ( @@ -82,12 +94,12 @@ export const Alert = ({
) : null} {children ? ( -
{children} -
+ ) : null} {links && links.length > 0 ? (