diff --git a/packages/terra-alert/CHANGELOG.md b/packages/terra-alert/CHANGELOG.md index 77ae978e5d4..e50b0eeb390 100644 --- a/packages/terra-alert/CHANGELOG.md +++ b/packages/terra-alert/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Added + * Added visually hidden default title for screen readers to read with custom titles. + ## 4.80.0 - (September 21, 2023) * Changed diff --git a/packages/terra-alert/package.json b/packages/terra-alert/package.json index 19f16a5c26f..17fa625bd7d 100644 --- a/packages/terra-alert/package.json +++ b/packages/terra-alert/package.json @@ -33,6 +33,7 @@ "terra-icon": "^3.57.1", "terra-responsive-element": "^5.37.0", "terra-theme-context": "^1.0.0", + "terra-visually-hidden-text": "^2.36.0", "uuid": "3.4.0" }, "scripts": { diff --git a/packages/terra-alert/src/Alert.jsx b/packages/terra-alert/src/Alert.jsx index 31575fe88af..96555886811 100644 --- a/packages/terra-alert/src/Alert.jsx +++ b/packages/terra-alert/src/Alert.jsx @@ -7,6 +7,7 @@ import { v4 as uuidv4 } from 'uuid'; import ResponsiveElement from 'terra-responsive-element'; import Button from 'terra-button'; +import VisuallyHiddenText from 'terra-visually-hidden-text'; import { IconAlert, IconError, @@ -202,6 +203,7 @@ const Alert = ({
{(title || defaultTitle) && ( + {title && defaultTitle && } {title || defaultTitle} )} diff --git a/packages/terra-alert/tests/jest/Alert.test.jsx b/packages/terra-alert/tests/jest/Alert.test.jsx index 76ff91b76c5..2ae2efbaf3c 100644 --- a/packages/terra-alert/tests/jest/Alert.test.jsx +++ b/packages/terra-alert/tests/jest/Alert.test.jsx @@ -10,6 +10,7 @@ import IconInformation from 'terra-icon/lib/icon/IconInformation'; import IconSuccess from 'terra-icon/lib/icon/IconSuccess'; import IconWarning from 'terra-icon/lib/icon/IconWarning'; import Button from 'terra-button'; +import VisuallyHiddenText from 'terra-visually-hidden-text'; import { v4 as uuidv4 } from 'uuid'; import Alert from '../../src/Alert'; @@ -66,6 +67,20 @@ describe('Alert with props', () => { expect(alert.prop('disableAlertActionFocus')).toEqual(true); expect(wrapper).toMatchSnapshot(); }); + + it('should render an alert with provided title', () => { + const wrapper = shallowWithIntl().dive(); + const alertTitle = wrapper.find('.title'); + + expect(alertTitle.prop('children')).toEqual( + [ + , + 'Custom Title', + ], + ); + expect(alertTitle.text()).toEqual('Custom Title'); + expect(wrapper).toMatchSnapshot(); + }); }); describe('Dismissible Alert that includes actions section', () => { @@ -73,10 +88,13 @@ describe('Dismissible Alert that includes actions section', () => { const mockOnDismiss = jest.fn(); const wrapper = shallowWithIntl(This is a test).dive(); - expect(wrapper.find(Button).length).toEqual(1); - expect(wrapper.find(Button).prop('text')).toEqual('Terra.alert.dismiss'); - expect(wrapper.find(Button).prop('onClick')).toEqual(mockOnDismiss); - expect(wrapper.find(Button).prop('variant')).toEqual('neutral'); + const dismissButton = wrapper.find(Button); + expect(wrapper.find('.title').text()).toEqual('Terra.alert.alert'); + expect(dismissButton.length).toEqual(1); + expect(dismissButton.prop('text')).toEqual('Terra.alert.dismiss'); + expect(dismissButton.prop('onClick')).toEqual(mockOnDismiss); + expect(dismissButton.prop('variant')).toEqual('neutral'); + expect(dismissButton.prop('aria-describedby')).toEqual('alert-title-00000000-0000-0000-0000-000000000000'); expect(wrapper).toMatchSnapshot(); }); }); @@ -199,11 +217,20 @@ describe('Alert of type custom with custom title and text content', () => { describe('Alert of type info with custom title and HTML content', () => { it('should render an Alert component of type info with custom title and HTML content', () => { - const wrapper = shallowWithIntl(Four score and seven years ago . . .); + const wrapper = shallowWithIntl(Four score and seven years ago . . .).dive(); - expect(wrapper.prop('title')).toEqual('Gettysburg Address'); - expect(wrapper.prop('type')).toEqual('info'); - expect(wrapper.find('span').text()).toEqual('Four score and seven years ago . . .'); + const alertDiv = wrapper.find('div.alert-base'); + expect(alertDiv.prop('className')).toEqual('alert-base info wide'); + expect(alertDiv.prop('role')).toEqual('status'); + expect(wrapper.find(IconInformation).length).toEqual(1); + expect(wrapper.find('.title').prop('children')).toEqual( + [ + , + 'Gettysburg Address', + ], + ); + expect(wrapper.find('.title').text()).toEqual('Gettysburg Address'); + expect(wrapper.find('.section').find('span').text()).toEqual('Four score and seven years ago . . .'); expect(wrapper).toMatchSnapshot(); }); }); @@ -226,7 +253,7 @@ describe('Alert of type success with an action button text content', () => { }); }); -describe('Dismissable Alert of type custom with action button, custom title and text content', () => { +describe('Dismissible Alert of type custom with action button, custom title and text content', () => { it('should render an Alert component of type custom with an action button', () => { const mockOnClick = jest.fn(); const mockOnDismiss = jest.fn(); @@ -251,10 +278,10 @@ describe('Dismissable Alert of type custom with action button, custom title and }); }); -describe('Dismissible Alert', () => { +describe('Notifications titles', () => { let wrapper; - describe('Custom Alert with no title prop', () => { + describe('Custom notification with no title prop', () => { beforeEach(() => { wrapper = shallowWithIntl( { , ).dive(); }); - it('should set the alert message ID', () => { + it('should set the notification message ID', () => { const alertContent = wrapper.find('.section'); expect(alertContent.prop('id')).toEqual('alert-message-00000000-0000-0000-0000-000000000000'); }); - it('should set the dismiss button aria-describedby to the alert description', () => { + it('should have no title', () => { + const alertTitle = wrapper.find('.title'); + expect(alertTitle.length).toEqual(0); + }); + + it('should set the dismiss button aria-describedby to the notification description', () => { const dismissButton = wrapper.find('Button'); expect(dismissButton.prop('aria-describedby')).toEqual('alert-message-00000000-0000-0000-0000-000000000000'); }); }); - describe('Custom Alert with custom title', () => { + describe('Custom notification with custom title', () => { beforeEach(() => { wrapper = shallowWithIntl( { expect(alertTitle.prop('id')).toEqual('alert-title-00000000-0000-0000-0000-000000000000'); }); + it('should set the alert title', () => { + const alertTitle = wrapper.find('.title'); + expect(alertTitle.prop('children')).toEqual(['', 'Help!']); + expect(alertTitle.text()).toEqual('Help!'); + }); + it('should set the dismiss button aria-describedby to the alert title', () => { const dismissButton = wrapper.find('Button'); expect(dismissButton.prop('aria-describedby')).toEqual('alert-title-00000000-0000-0000-0000-000000000000'); }); }); - describe('Success Alert with no title prop', () => { + describe('Success notification with no title prop', () => { beforeEach(() => { wrapper = shallowWithIntl( { ).dive(); }); - it('should set the alert message ID', () => { + it('should set the notification message ID', () => { const alertContent = wrapper.find('.section'); expect(alertContent.prop('id')).toEqual('alert-message-00000000-0000-0000-0000-000000000000'); }); - it('should set the alert title ID', () => { + it('should set the notification title ID', () => { const alertTitle = wrapper.find('.title'); expect(alertTitle.prop('id')).toEqual('alert-title-00000000-0000-0000-0000-000000000000'); }); - it('should set the dismiss button aria-describedby to the alert title', () => { + it('should use the default title', () => { + const alertTitle = wrapper.find('.title'); + expect(alertTitle.prop('children')).toEqual([undefined, 'Terra.alert.success']); + expect(alertTitle.text()).toEqual('Terra.alert.success'); + }); + + it('should set the dismiss button aria-describedby to the notification title', () => { const dismissButton = wrapper.find('Button'); expect(dismissButton.prop('aria-describedby')).toEqual('alert-title-00000000-0000-0000-0000-000000000000'); }); }); - describe('Success Alert with blank title', () => { + describe('Success notification with blank title', () => { beforeEach(() => { wrapper = shallowWithIntl( { it('should use the default title', () => { const alertTitle = wrapper.find('.title'); - expect(alertTitle.prop('children')).toEqual('Terra.alert.success'); + expect(alertTitle.prop('children')).toEqual(['', 'Terra.alert.success']); + expect(alertTitle.text()).toEqual('Terra.alert.success'); }); - it('should set the alert message ID', () => { + it('should set the notification message ID', () => { const alertContent = wrapper.find('.section'); expect(alertContent.prop('id')).toEqual('alert-message-00000000-0000-0000-0000-000000000000'); }); - it('should set the alert title ID', () => { + it('should set the notification title ID', () => { const alertTitle = wrapper.find('.title'); expect(alertTitle.prop('id')).toEqual('alert-title-00000000-0000-0000-0000-000000000000'); }); - it('should set the dismiss button aria-describedby to the alert title', () => { + it('should set the dismiss button aria-describedby to the notification title', () => { + const dismissButton = wrapper.find('Button'); + expect(dismissButton.prop('aria-describedby')).toEqual('alert-title-00000000-0000-0000-0000-000000000000'); + }); + }); + + describe('Success notification with title', () => { + beforeEach(() => { + wrapper = shallowWithIntl( + { }} + > + This is a success alert. + , + ).dive(); + }); + + it('should use the custom title', () => { + const alertTitle = wrapper.find('.title'); + expect(alertTitle.prop('children')).toEqual( + [ + , + 'Custom Success Title', + ], + ); + expect(alertTitle.text()).toEqual('Custom Success Title'); + }); + + it('should set the notification message ID', () => { + const alertContent = wrapper.find('.section'); + expect(alertContent.prop('id')).toEqual('alert-message-00000000-0000-0000-0000-000000000000'); + }); + + it('should set the notification title ID', () => { + const alertTitle = wrapper.find('.title'); + expect(alertTitle.prop('id')).toEqual('alert-title-00000000-0000-0000-0000-000000000000'); + }); + + it('should set the dismiss button aria-describedby to the notification title', () => { const dismissButton = wrapper.find('Button'); expect(dismissButton.prop('aria-describedby')).toEqual('alert-title-00000000-0000-0000-0000-000000000000'); }); diff --git a/packages/terra-alert/tests/jest/__snapshots__/Alert.test.jsx.snap b/packages/terra-alert/tests/jest/__snapshots__/Alert.test.jsx.snap index 7ba97c2fc2a..4f73464c3b7 100644 --- a/packages/terra-alert/tests/jest/__snapshots__/Alert.test.jsx.snap +++ b/packages/terra-alert/tests/jest/__snapshots__/Alert.test.jsx.snap @@ -173,43 +173,53 @@ exports[`Alert of type error with text content should render an Alert component `; exports[`Alert of type info with custom title and HTML content should render an Alert component of type info with custom title and HTML content 1`] = ` - - - Four score and seven years ago . . . - - +
+
+
+
+ + + +
+ + + Gettysburg Address + + + Four score and seven years ago . . . + +
+
+
+
+ `; exports[`Alert of type info with text content should render an Alert component of type info 1`] = ` @@ -722,6 +732,53 @@ exports[`Alert with props should render an alert with provided role 1`] = ` `; +exports[`Alert with props should render an alert with provided title 1`] = ` + +
+
+
+
+ + + +
+ + + Custom Title + +
+
+
+
+ +`; + exports[`Alert with props should render disableAlertActionFocus when provided 1`] = ` `; -exports[`Dismissable Alert of type custom with action button, custom title and text content should render an Alert component of type custom with an action button 1`] = ` +exports[`Dismissible Alert of type custom with action button, custom title and text content should render an Alert component of type custom with an action button 1`] = `