Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alert - don't use role or aria-live for non-valid alert usages (HDS-3919) #2500

Merged
merged 8 commits into from
Oct 18, 2024
5 changes: 5 additions & 0 deletions .changeset/tough-lies-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashicorp/design-system-components": patch
---

`Alert` - Removed role="alert" and aria-live="polite" attributes from Alerts with color set to "neutral" or "highlight"
2 changes: 1 addition & 1 deletion packages/components/src/components/hds/alert/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<div
class={{this.classNames}}
role={{this.role}}
aria-live="polite"
aria-live={{if this.role "polite"}}
aria-labelledby={{this.ariaLabelledBy}}
{{did-insert this.didInsert}}
...attributes
Expand Down
43 changes: 14 additions & 29 deletions packages/components/src/components/hds/alert/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface HdsAlertSignature {
}

export default class HdsAlert extends Component<HdsAlertSignature> {
@tracked role = 'alert';
@tracked role?: string;
@tracked ariaLabelledBy?: string;

constructor(owner: unknown, args: HdsAlertSignature['Args']) {
Expand All @@ -76,12 +76,7 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
);
}

/**
* @param color
* @type {enum}
* @default neutral
* @description Determines the color scheme for the alert.
*/
// Determines the color scheme for the alert.
get color(): HdsAlertColors {
const { color = DEFAULT_COLOR } = this.args;

Expand All @@ -95,12 +90,7 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
return color;
}

/**
* @param icon
* @type {string}
* @default false
* @description The name of the icon to be used.
*/
// The name of the icon to be used.
get icon(): HdsIconSignature['Args']['name'] | false {
const { icon } = this.args;

Expand All @@ -127,11 +117,6 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
}
}

/**
* @param onDismiss
* @type {function}
* @default () => {}
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get onDismiss(): ((event: MouseEvent, ...args: any[]) => void) | false {
const { onDismiss } = this.args;
Expand All @@ -143,11 +128,7 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
}
}

/**
* @param iconSize
* @type {string}
* @description ensures that the correct icon size is used. Automatically calculated.
*/
// Ensures that the correct icon size is used. Automatically calculated.
get iconSize(): HdsIconSignature['Args']['size'] {
if (this.args.type === 'compact') {
return '16';
Expand All @@ -156,11 +137,6 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
}
}

/**
* Get the class names to apply to the component.
* @method Alert#classNames
* @return {string} The "class" attribute to apply to the component.
*/
get classNames(): string {
const classes = ['hds-alert'];

Expand All @@ -178,8 +154,17 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
const actions = element.querySelectorAll(
`${CONTENT_ELEMENT_SELECTOR} button, ${CONTENT_ELEMENT_SELECTOR} a`
);
if (actions.length) {

// an Alert which actually alerts users (has role="alert" & aria-live="polite") as opposed to an informational or promo "alert"
const isRealAlert: boolean =
KristinLBradley marked this conversation as resolved.
Show resolved Hide resolved
this.color === 'warning' ||
this.color === 'critical' ||
this.color === 'success';

if (isRealAlert && actions.length) {
this.role = 'alertdialog';
} else if (isRealAlert) {
this.role = 'alert';
}

// `alertdialog` must have an accessible name so we use either the
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/components/hds/toast/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
@color={{@color}}
@icon={{@icon}}
@onDismiss={{@onDismiss}}
role="alert"
aria-live="polite"
...attributes
as |A|
>
Expand Down
127 changes: 116 additions & 11 deletions showcase/tests/integration/components/hds/alert/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,37 +145,142 @@ module('Integration | Component | hds/alert/index', function (hooks) {

// A11Y

test('it should render with the correct semantic tags and aria attributes', async function (assert) {
await render(hbs`<Hds::Alert @type="inline" id="test-alert" />`);
// * Colors for alert usages which notify users: success, warning, critical

test('it should render the component with role="alert" and aria-live="polite" for the "success" color', async function (assert) {
await render(
hbs`<Hds::Alert @type="inline" @color="success" id="test-alert" />`
);
assert.dom('#test-alert').hasAttribute('role', 'alert');
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
});

test('it should render the component with role="alert" and aria-live="polite" for the "warning" color', async function (assert) {
await render(
hbs`<Hds::Alert @type="inline" @color="warning" id="test-alert" />`
);
assert.dom('#test-alert').hasAttribute('role', 'alert');
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
});

test('it should render the component with role="alert" and aria-live="polite" for the "critical" color', async function (assert) {
await render(
hbs`<Hds::Alert @type="inline" @color="critical" id="test-alert" />`
);
assert.dom('#test-alert').hasAttribute('role', 'alert');
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
});

// * Colors for informational & promo usages: neutral, highlight

test('it should not render the component with role="alert" and aria-live="polite" for the "neutral" color', async function (assert) {
await render(
hbs`<Hds::Alert @type="inline" @color="neutral" id="test-alert" />`
);
assert.dom('#test-alert').doesNotHaveAttribute('role', 'alert');
assert.dom('#test-alert').doesNotHaveAttribute('aria-live', 'polite');
});

test('it should not render the component with role="alert" and aria-live="polite" for the "highlight" color', async function (assert) {
await render(
hbs`<Hds::Alert @type="inline" @color="highlight" id="test-alert" />`
);
assert.dom('#test-alert').doesNotHaveAttribute('role', 'alert');
assert.dom('#test-alert').doesNotHaveAttribute('aria-live', 'polite');
});

test('it should render with an `alertdialog` role and auto-generated `aria-labelledby` when title and actions are provided', async function (assert) {
// aria-labelledby

test('it should render with an auto-generated `aria-labelledby` when a title is provided', async function (assert) {
await render(
hbs`<Hds::Alert @type="inline" id="test-alert" as |A|><A.Title>This is the title</A.Title><A.Button @text="I am a button" @size="small" /></Hds::Alert>`
hbs`
<Hds::Alert @type="inline" id="test-alert" as |A|>
<A.Title>This is the title</A.Title>
</Hds::Alert>
`
);
let title = this.element.querySelector('#test-alert .hds-alert__title');
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
assert.dom('#test-alert').hasAttribute('aria-labelledby', title.id);
});

test('it should render with an `alertdialog` role and auto-generated `aria-labelledby` when description and actions are provided', async function (assert) {
test('it should render with an auto-generated `aria-labelledby` when description is provided', async function (assert) {
await render(
hbs`<Hds::Alert @type="inline" id="test-alert" as |A|><A.Description>This is the title</A.Description><A.Button @text="I am a button" @size="small" /></Hds::Alert>`
hbs`
<Hds::Alert @type="inline" id="test-alert" as |A|>
<A.Description>This is the title</A.Description>
</Hds::Alert>
`
);
let description = this.element.querySelector(
'#test-alert .hds-alert__description'
);
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
assert.dom('#test-alert').hasAttribute('aria-labelledby', description.id);
});

test('it should render with an `alertdialog` role and `aria-labelledby` when title and actions are provided', async function (assert) {
// Alert dialogs

// * Colors for alert usages which notify users: success, warning, critical

test('it should render with with role="alertdialog" and aria-live="polite" for the "success" color when actions are provided', async function (assert) {
await render(
hbs`
<Hds::Alert @type="inline" @color="success" id="test-alert" as |A|>
<A.Button @text="I am a button" @size="small" />
</Hds::Alert>
`
);
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
});

test('it should render with with role="alertdialog" and aria-live="polite" for the "warning" color when actions are provided', async function (assert) {
await render(
hbs`<Hds::Alert @type="inline" id="test-alert" as |A|><A.Title id="custom-id">This is the title</A.Title><A.Button @text="I am a button" @size="small" /></Hds::Alert>`
hbs`
<Hds::Alert @type="inline" @color="warning" id="test-alert" as |A|>
<A.Button @text="I am a button" @size="small" />
</Hds::Alert>
`
);
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
assert.dom('#test-alert').hasAttribute('aria-labelledby', 'custom-id');
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
});

test('it should render with with role="alertdialog" and aria-live="polite" for the "critical" color when actions are provided', async function (assert) {
await render(
hbs`
<Hds::Alert @type="inline" @color="critical" id="test-alert" as |A|>
<A.Button @text="I am a button" @size="small" />
</Hds::Alert>
`
);
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
});

// * Colors for informational & promo usages: neutral, highlight

test('it should not render with with role="alertdialog" and aria-live="polite" for the "neutral" color when actions are provided', async function (assert) {
await render(
hbs`
<Hds::Alert @type="inline" @color="neutral" id="test-alert" as |A|>
<A.Button @text="I am a button" @size="small" />
</Hds::Alert>
`
);
assert.dom('#test-alert').doesNotHaveAttribute('role', 'alertdialog');
assert.dom('#test-alert').doesNotHaveAttribute('aria-live', 'polite');
});

test('it should not render with with role="alertdialog" and aria-live="polite" for the "highlight" color when actions are provided', async function (assert) {
await render(
hbs`
<Hds::Alert @type="inline" @color="highlight" id="test-alert" as |A|>
<A.Button @text="I am a button" @size="small" />
</Hds::Alert>
`
);
assert.dom('#test-alert').doesNotHaveAttribute('role', 'alertdialog');
assert.dom('#test-alert').doesNotHaveAttribute('aria-live', 'polite');
});

// ASSERTIONS
Expand Down
6 changes: 6 additions & 0 deletions showcase/tests/integration/components/hds/toast/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,10 @@ module('Integration | Component | hds/toast/index', function (hooks) {
await render(hbs`<Hds::Toast id="test-toast" />`);
assert.dom('#test-toast').hasClass('hds-toast');
});

test('it should render the component with "role"="alert" and aria-live="polite" by default', async function (assert) {
await render(hbs`<Hds::Toast id="test-toast" />`);
assert.dom('#test-toast').hasAttribute('role', 'alert');
assert.dom('#test-toast').hasAttribute('aria-live', 'polite');
});
});
Loading