Skip to content

Commit

Permalink
chore: Adds i18n strings prop to the alert component (#3167)
Browse files Browse the repository at this point in the history
  • Loading branch information
orangevolon authored Jan 8, 2025
1 parent f7988a7 commit 1c32dd6
Show file tree
Hide file tree
Showing 26 changed files with 323 additions and 51 deletions.
11 changes: 11 additions & 0 deletions pages/alert/common.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { AlertProps } from '~components';

export const i18nStrings: AlertProps.I18nStrings = {
dismissAriaLabel: 'Close alert',
errorIconAriaLabel: 'error',
warningIconAriaLabel: 'warning',
infoIconAriaLabel: 'info',
successIconAriaLabel: 'success',
};
17 changes: 9 additions & 8 deletions pages/alert/permutations.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Link from '~components/link';
import createPermutations from '../utils/permutations';
import PermutationsView from '../utils/permutations-view';
import ScreenshotArea from '../utils/screenshot-area';
import { i18nStrings } from './common';

const longText =
'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
Expand Down Expand Up @@ -53,31 +54,33 @@ const allTypes: AlertProps.Type[] = ['info', 'success', 'warning', 'error'];
/* eslint-disable react/jsx-key */
const permutations = createPermutations<AlertProps>([
{
i18nStrings: [i18nStrings],
children: [longText, longTextWithLink],
type: allTypes,
},
{
dismissAriaLabel: ['Close alert'],
i18nStrings: [i18nStrings],
dismissible: [true],
header: ['Default Example Header'],
type: allTypes,
},
{
i18nStrings: [i18nStrings],
buttonText: ['Button text'],
children: ['Default Example Body'],
type: allTypes,
},
{
i18nStrings: [i18nStrings],
dismissible: [true],
dismissAriaLabel: ['Close alert'],
buttonText: ['Button text'],
header: ['Default Example Header', longText],
children: ['Default Example Body', longText],
type: allTypes,
},
{
i18nStrings: [i18nStrings],
dismissible: [true, false],
dismissAriaLabel: ['Close alert'],
header: [undefined, 'Default Example Header'],
children: ['Default Example Body', longText],
action: [
Expand All @@ -88,16 +91,17 @@ const permutations = createPermutations<AlertProps>([
type: allTypes,
},
{
i18nStrings: [i18nStrings],
header: ['With expandable section'],
children: [longTextWithExpandableSection],
type: ['info'],
},
{
i18nStrings: [i18nStrings],
header: ['With unbreakable word'],
children: [longTextWithUnbreakableWord],
type: ['info'],
dismissible: [true, false],
dismissAriaLabel: ['Close alert'],
action: [undefined, <Button>Action</Button>],
},
]);
Expand All @@ -107,10 +111,7 @@ export default function AlertScenario() {
<article>
<h1>Alert permutations</h1>
<ScreenshotArea>
<PermutationsView
permutations={permutations}
render={permutation => <Alert statusIconAriaLabel={permutation.type ?? 'Info'} {...permutation} />}
/>
<PermutationsView permutations={permutations} render={permutation => <Alert {...permutation} />} />
</ScreenshotArea>
</article>
);
Expand Down
9 changes: 3 additions & 6 deletions pages/alert/runtime-action.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import SpaceBetween from '~components/space-between';
import createPermutations from '../utils/permutations';
import PermutationsView from '../utils/permutations-view';
import ScreenshotArea from '../utils/screenshot-area';
import { i18nStrings } from './common';

awsuiPlugins.alert.registerAction({
id: 'awsui/alert-test-action',
Expand Down Expand Up @@ -44,6 +45,7 @@ awsuiPlugins.alert.registerAction({
const permutations = createPermutations<AlertProps>([
{
dismissible: [true, false],
i18nStrings: [i18nStrings],
header: ['Alert'],
children: ['Content'],
type: ['success', 'error'],
Expand All @@ -64,12 +66,7 @@ export default function () {
<>
<h1>Alert runtime actions</h1>
<ScreenshotArea>
<PermutationsView
permutations={permutations}
render={permutation => (
<Alert statusIconAriaLabel={permutation.type} dismissAriaLabel="Dismiss" {...permutation} />
)}
/>
<PermutationsView permutations={permutations} render={permutation => <Alert {...permutation} />} />
</ScreenshotArea>
</>
);
Expand Down
12 changes: 3 additions & 9 deletions pages/alert/runtime-content.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import awsuiPlugins from '~components/internal/plugins';

import AppContext, { AppContextType } from '../app/app-context';
import ScreenshotArea from '../utils/screenshot-area';
import { i18nStrings } from './common';

type PageContext = React.Context<
AppContextType<{ loading: boolean; hidden: boolean; type: AlertProps.Type; autofocus: boolean }>
Expand Down Expand Up @@ -134,20 +135,13 @@ export default function () {
<ScreenshotArea gutters={false}>
{hidden ? null : (
<SpaceBetween size="m">
<Alert
type={type}
statusIconAriaLabel={type}
dismissAriaLabel="Dismiss"
header="Header"
action={<Button>Action</Button>}
>
<Alert type={type} i18nStrings={i18nStrings} header="Header" action={<Button>Action</Button>}>
{!contentSwapped ? content1 : content2}
</Alert>

<Alert
type={type}
statusIconAriaLabel={type}
dismissAriaLabel="Dismiss"
i18nStrings={i18nStrings}
header="Header"
action={<Button>Action</Button>}
ref={alertRef}
Expand Down
5 changes: 3 additions & 2 deletions pages/alert/simple.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Link from '~components/link';
import SpaceBetween from '~components/space-between';

import ScreenshotArea from '../utils/screenshot-area';
import { i18nStrings } from './common';

import styles from './styles.scss';

Expand All @@ -34,7 +35,7 @@ export default function AlertScenario() {
<Alert
header="This is going to be an extremely long title for an alert not sure whether it makes any sense but whatever"
visible={visible}
statusIconAriaLabel="Warning"
i18nStrings={i18nStrings}
dismissible={true}
buttonText="Button text"
type="warning"
Expand All @@ -48,7 +49,7 @@ export default function AlertScenario() {
<Link href="#">This is a secondary link</Link>
</Alert>
</div>
<Alert header="Info" statusIconAriaLabel="Info">
<Alert header="Info" i18nStrings={i18nStrings}>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
Expand Down
42 changes: 42 additions & 0 deletions src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ when the \`dismissible\` property is set to \`true\`.",
"type": "string",
},
{
"deprecatedTag": "Use \`i18nStrings.dismissAriaLabel\` instead.
If the label is assigned via the \`i18nStrings\` property, this label will be ignored.",
"description": "Adds an aria-label to the dismiss button.",
"i18nTag": true,
"name": "dismissAriaLabel",
Expand All @@ -47,6 +49,44 @@ An \`onDismiss\` event is fired when a user clicks the button.",
"optional": true,
"type": "boolean",
},
{
"description": "An object containing all the necessary localized strings required by the component.",
"i18nTag": true,
"inlineType": {
"name": "AlertProps.I18nStrings",
"properties": [
{
"name": "dismissAriaLabel",
"optional": true,
"type": "string",
},
{
"name": "errorIconAriaLabel",
"optional": true,
"type": "string",
},
{
"name": "infoIconAriaLabel",
"optional": true,
"type": "string",
},
{
"name": "successIconAriaLabel",
"optional": true,
"type": "string",
},
{
"name": "warningIconAriaLabel",
"optional": true,
"type": "string",
},
],
"type": "object",
},
"name": "i18nStrings",
"optional": true,
"type": "AlertProps.I18nStrings",
},
{
"deprecatedTag": "The usage of the \`id\` attribute is reserved for internal use cases. For testing and other use cases,
use [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes). If you must
Expand All @@ -57,6 +97,8 @@ use the \`id\` attribute, consider setting it on a parent element instead.",
"type": "string",
},
{
"deprecatedTag": "Use the label properties inside \`i18nStrings\` instead.
If the label is assigned via the \`i18nStrings\` property, this label will be ignored.",
"description": "Provides a text alternative for the icon.",
"name": "statusIconAriaLabel",
"optional": true,
Expand Down
82 changes: 76 additions & 6 deletions src/alert/__tests__/alert.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { render } from '@testing-library/react';
import '../../__a11y__/to-validate-a11y';
import Alert, { AlertProps } from '../../../lib/components/alert';
import Button from '../../../lib/components/button';
import TestI18nProvider from '../../../lib/components/i18n/testing';
import { DATA_ATTR_ANALYTICS_ALERT } from '../../../lib/components/internal/analytics/selectors';
import { useVisualRefresh } from '../../../lib/components/internal/hooks/use-visual-mode';
import createWrapper from '../../../lib/components/test-utils/dom';
Expand All @@ -22,6 +23,14 @@ function renderAlert(props: AlertProps = {}) {
return { wrapper: createWrapper(container).findAlert()!, container };
}

const i18nStrings: AlertProps.I18nStrings = {
successIconAriaLabel: 'status: success',
infoIconAriaLabel: 'status: info',
warningIconAriaLabel: 'status: warning',
errorIconAriaLabel: 'status: error',
dismissAriaLabel: 'dismiss',
};

beforeEach(() => {
jest.mocked(useVisualRefresh).mockReset();
});
Expand Down Expand Up @@ -71,16 +80,16 @@ describe('Alert Component', () => {
expect(wrapper.findDismissButton()!.getElement()).not.toHaveAttribute('aria-label');
});
it('dismiss button can have specified label', () => {
const { wrapper } = renderAlert({ dismissible: true, dismissAriaLabel: 'close' });
expect(wrapper.findDismissButton()!.getElement()).toHaveAttribute('aria-label', 'close');
const { wrapper } = renderAlert({ dismissible: true, i18nStrings });
expect(wrapper.findDismissButton()!.getElement()).toHaveAttribute('aria-label', 'dismiss');
});
it('status icon does not have a label by default', () => {
const { wrapper } = renderAlert({});
expect(wrapper.find('[role="img"]')!.getElement()).not.toHaveAttribute('aria-label');
});
it('status icon can have a label', () => {
const { wrapper } = renderAlert({ statusIconAriaLabel: 'Info' });
expect(wrapper.find('[role="img"]')!.getElement()).toHaveAttribute('aria-label', 'Info');
const { wrapper } = renderAlert({ i18nStrings });
expect(wrapper.find('[role="img"]')!.getElement()).toHaveAttribute('aria-label', 'status: info');
});
});
describe('visibility', () => {
Expand Down Expand Up @@ -145,8 +154,7 @@ describe('Alert Component', () => {
const { container } = renderAlert({
dismissible: true,
header: 'Header',
dismissAriaLabel: 'Dismiss',
statusIconAriaLabel: 'Icon',
i18nStrings,
action: <button type="button">Action</button>,
});
await expect(container).toValidateA11y();
Expand Down Expand Up @@ -187,4 +195,66 @@ describe('Alert Component', () => {
expect(wrapper.findByClassName(styles['icon-size-normal'])).toBeTruthy();
});
});

describe('i18n', () => {
const alertTypes: AlertProps.Type[] = ['info', 'success', 'error', 'warning'];
const i18nMessages = {
alert: {
'i18nStrings.successIconAriaLabel': 'success default label',
'i18nStrings.infoIconAriaLabel': 'info default label',
'i18nStrings.warningIconAriaLabel': 'warning default label',
'i18nStrings.errorIconAriaLabel': 'error default label',
'i18nStrings.dismissAriaLabel': 'dismiss default label',
},
};

function renderAlertForI18n(props: AlertProps = {}) {
const { container } = render(
<TestI18nProvider messages={i18nMessages}>
<Alert {...props} />
</TestI18nProvider>
);
const wrapper = createWrapper(container)!.findAlert()!;
const statusIcon = wrapper.findByClassName(styles.icon)!.getElement();
const dismissButton = wrapper.findDismissButton()!.getElement();
return { statusIcon, dismissButton };
}

describe.each(alertTypes)('alert type: %s', type => {
it('assigns the specified aria labels via i18nStrings prop', () => {
const { statusIcon, dismissButton } = renderAlertForI18n({ dismissible: true, type, i18nStrings });
expect(statusIcon).toHaveAccessibleName(`status: ${type}`);
expect(dismissButton).toHaveAccessibleName('dismiss');
});

it('assigns the labels from i18n provider, when not specified', () => {
const { statusIcon, dismissButton } = renderAlertForI18n({ dismissible: true, type });
expect(statusIcon).toHaveAccessibleName(`${type} default label`);
expect(dismissButton).toHaveAccessibleName('dismiss default label');
});
});

describe('deprecated aria labels', () => {
it('ignores the deprecated values if i18nStrings is specified', () => {
const { statusIcon, dismissButton } = renderAlertForI18n({
dismissible: true,
dismissAriaLabel: 'deprecated dismiss label',
statusIconAriaLabel: 'deprecated status icon label',
i18nStrings,
});
expect(statusIcon).toHaveAccessibleName('status: info');
expect(dismissButton).toHaveAccessibleName('dismiss');
});

it('uses the deprecated values if i18nStrings is not specified', () => {
const { statusIcon, dismissButton } = renderAlertForI18n({
dismissible: true,
dismissAriaLabel: 'deprecated dismiss label',
statusIconAriaLabel: 'deprecated status icon label',
});
expect(statusIcon).toHaveAccessibleName('deprecated status icon label');
expect(dismissButton).toHaveAccessibleName('deprecated dismiss label');
});
});
});
});
2 changes: 1 addition & 1 deletion src/alert/__tests__/analytics-metadata.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ beforeAll(() => {
});
describe('Alert renders correct analytics metadata', () => {
test('on dismiss button', () => {
const wrapper = renderAlert({ dismissible: true, dismissAriaLabel: 'dismiss label' });
const wrapper = renderAlert({ dismissible: true, i18nStrings: { dismissAriaLabel: 'dismiss label' } });
const dismissButton = wrapper.findDismissButton()!.getElement();
validateComponentNameAndLabels(dismissButton, labels);
expect(getGeneratedAnalyticsMetadata(dismissButton)).toEqual({
Expand Down
Loading

0 comments on commit 1c32dd6

Please sign in to comment.