From 74ef2ab6d1d2ae38eb5b3a8005274773af668684 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Thu, 26 Jul 2018 12:58:57 -0700 Subject: [PATCH 1/7] chore(release): publish - @zendeskgarden/react-ranges@2.2.11 --- packages/ranges/CHANGELOG.md | 11 +++++++++++ packages/ranges/package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/ranges/CHANGELOG.md b/packages/ranges/CHANGELOG.md index 8047b2476f1..926f10cc3f0 100644 --- a/packages/ranges/CHANGELOG.md +++ b/packages/ranges/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [2.2.11](https://github.com/zendeskgarden/react-components/compare/@zendeskgarden/react-ranges@2.2.10...@zendeskgarden/react-ranges@2.2.11) (2018-07-26) + + +### Bug Fixes + +* **ranges:** increase specificity to show selection fill color ([#69](https://github.com/zendeskgarden/react-components/issues/69)) ([8914ce3](https://github.com/zendeskgarden/react-components/commit/8914ce3)) + + + + ## [2.2.10](https://github.com/zendeskgarden/react-components/compare/@zendeskgarden/react-ranges@2.2.9...@zendeskgarden/react-ranges@2.2.10) (2018-07-25) diff --git a/packages/ranges/package.json b/packages/ranges/package.json index e7f0942b1fb..d83f5421a05 100644 --- a/packages/ranges/package.json +++ b/packages/ranges/package.json @@ -8,7 +8,7 @@ "bugs": { "url": "https://github.com/zendeskgarden/react-components/issues" }, - "version": "2.2.10", + "version": "2.2.11", "main": "./dist/index.js", "files": [ "dist" From 3da98425904ba257b42833d051c8feb92a047585 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Tue, 24 Jul 2018 15:25:00 -0700 Subject: [PATCH 2/7] feat(accessibility): assorted accessibility upgrades --- .../src/containers/AutocompleteContainer.js | 16 ++-------- .../containers/AutocompleteContainer.spec.js | 2 -- .../src/containers/ButtonGroupContainer.js | 3 +- .../containers/ButtonGroupContainer.spec.js | 11 +++++++ packages/checkboxes/src/elements/Checkbox.js | 10 +++++- .../checkboxes/src/elements/Checkbox.spec.js | 2 +- .../menus/src/containers/MenuContainer.js | 14 ++++---- .../src/containers/MenuContainer.spec.js | 7 +++- .../src/containers/PaginationContainer.js | 5 ++- .../containers/PaginationContainer.spec.js | 4 +-- packages/radios/src/elements/Radio.js | 10 +++++- packages/radios/src/elements/Radio.spec.js | 2 +- packages/ranges/src/elements/RangeField.js | 10 +++++- .../ranges/src/elements/RangeField.spec.js | 2 +- .../select/src/containers/SelectContainer.js | 18 ++--------- .../src/containers/SelectContainer.spec.js | 15 --------- packages/select/src/elements/SelectField.js | 13 +++++++- .../src/containers/FieldContainer.js | 10 +++--- .../src/containers/FieldContainer.spec.js | 21 +++++++++--- .../src/containers/SelectionContainer.js | 9 ++++-- .../src/containers/SelectionContainer.spec.js | 32 +++++++++++++------ packages/textfields/src/elements/TextField.js | 10 +++++- .../textfields/src/elements/TextField.spec.js | 2 +- packages/textfields/src/views/Input.js | 5 +++ .../views/__snapshots__/Input.spec.js.snap | 14 ++++++++ .../views/__snapshots__/Textarea.spec.js.snap | 11 +++++++ packages/toggles/src/elements/Toggle.js | 10 +++++- packages/toggles/src/elements/Toggle.spec.js | 2 +- packages/toggles/src/views/Input.js | 2 +- packages/tooltips/src/elements/Tooltip.js | 12 +++++-- 30 files changed, 186 insertions(+), 98 deletions(-) diff --git a/packages/autocomplete/src/containers/AutocompleteContainer.js b/packages/autocomplete/src/containers/AutocompleteContainer.js index 5594d8425de..04768d4510b 100644 --- a/packages/autocomplete/src/containers/AutocompleteContainer.js +++ b/packages/autocomplete/src/containers/AutocompleteContainer.js @@ -317,12 +317,10 @@ class AutocompleteContainer extends ControlledComponent { }; getItemId = key => - typeof key === 'undefined' ? '' : `${this.getControlledState().id}--item-${key}`; + typeof key === 'undefined' ? null : `${this.getControlledState().id}--item-${key}`; getTagId = key => - typeof key === 'undefined' ? '' : `${this.getControlledState().id}--tag-${key}`; - - getMenuId = () => `${this.getControlledState().id}--menu`; + typeof key === 'undefined' ? null : `${this.getControlledState().id}--tag-${key}`; getInputProps = ({ tabIndex = 0, @@ -340,7 +338,6 @@ class AutocompleteContainer extends ControlledComponent { role, 'aria-autocomplete': 'list', 'aria-haspopup': 'true', - 'aria-owns': this.getMenuId(), 'aria-expanded': isOpen, 'aria-activedescendant': isOpen ? this.getItemId(focusedKey) : this.getTagId(tagFocusedKey), autoComplete: 'off', @@ -387,15 +384,8 @@ class AutocompleteContainer extends ControlledComponent { }; }; - getMenuProps = ({ - id = this.getMenuId(), - role = 'listbox', - onMouseDown, - onMouseUp, - ...other - } = {}) => { + getMenuProps = ({ role = 'listbox', onMouseDown, onMouseUp, ...other } = {}) => { return { - id, role, onMouseDown: composeEventHandlers(onMouseDown, () => { this.menuMousedDown = true; diff --git a/packages/autocomplete/src/containers/AutocompleteContainer.spec.js b/packages/autocomplete/src/containers/AutocompleteContainer.spec.js index 63354ab1451..669fa6de2d5 100644 --- a/packages/autocomplete/src/containers/AutocompleteContainer.spec.js +++ b/packages/autocomplete/src/containers/AutocompleteContainer.spec.js @@ -217,7 +217,6 @@ describe('AutocompleteContainer', () => { expect(input).toHaveProp('role', 'combobox'); expect(input).toHaveProp('aria-autocomplete', 'list'); expect(input).toHaveProp('aria-haspopup', 'true'); - expect(input).toHaveProp('aria-owns', 'test--menu'); expect(input).toHaveProp('autoComplete', 'off'); }); @@ -494,7 +493,6 @@ describe('AutocompleteContainer', () => { it('applies accessibility attributes correctly', () => { const menu = findMenu(wrapper); - expect(menu).toHaveProp('id', 'test--menu'); expect(menu).toHaveProp('role', 'listbox'); }); diff --git a/packages/buttons/src/containers/ButtonGroupContainer.js b/packages/buttons/src/containers/ButtonGroupContainer.js index 185debbea1a..7ac78301f05 100644 --- a/packages/buttons/src/containers/ButtonGroupContainer.js +++ b/packages/buttons/src/containers/ButtonGroupContainer.js @@ -101,7 +101,8 @@ export default class ButtonGroupContainer extends ControlledComponent { {({ getContainerProps, getItemProps }) => render({ getGroupProps: props => getContainerProps(this.getGroupProps(props)), - getButtonProps: props => getItemProps(this.getButtonProps(props)), + getButtonProps: props => + getItemProps(this.getButtonProps(props), { selectedAriaKey: 'aria-pressed' }), focusedKey, selectedKey }) diff --git a/packages/buttons/src/containers/ButtonGroupContainer.spec.js b/packages/buttons/src/containers/ButtonGroupContainer.spec.js index 17042a88b68..2e4661ca067 100644 --- a/packages/buttons/src/containers/ButtonGroupContainer.spec.js +++ b/packages/buttons/src/containers/ButtonGroupContainer.spec.js @@ -81,6 +81,17 @@ describe('ButtonGroupContainer', () => { }); }); + it('applies the correct accessibility selected value when not selected', () => { + expect(findButtons(wrapper).first()).toHaveProp('aria-pressed', false); + }); + + it('applies the correct accessibility selected value when selected', () => { + findButtons(wrapper) + .first() + .simulate('click'); + expect(findButtons(wrapper).first()).toHaveProp('aria-pressed', true); + }); + it('moves focus to the ButtonGroupView if a button receives focus', () => { findButtons(wrapper) .at(0) diff --git a/packages/checkboxes/src/elements/Checkbox.js b/packages/checkboxes/src/elements/Checkbox.js index 597213ee1e9..f284851214f 100644 --- a/packages/checkboxes/src/elements/Checkbox.js +++ b/packages/checkboxes/src/elements/Checkbox.js @@ -65,9 +65,17 @@ export default class Checkbox extends ControlledComponent { */ const { onMouseDown: onFocusMouseDown, ...checkboxProps } = getFocusProps(checkboxInputProps); + let isDescribed = false; + + Children.forEach(children, child => { + if (hasType(child, Hint)) { + isDescribed = true; + } + }); + return ( - + {Children.map(children, child => { if (!isValidElement(child)) { return child; diff --git a/packages/checkboxes/src/elements/Checkbox.spec.js b/packages/checkboxes/src/elements/Checkbox.spec.js index 41105c07684..787767015ff 100644 --- a/packages/checkboxes/src/elements/Checkbox.spec.js +++ b/packages/checkboxes/src/elements/Checkbox.spec.js @@ -42,7 +42,7 @@ describe('Checkbox', () => { }); it('applies container props to Message', () => { - expect(wrapper.find(Message)).toHaveProp('id', `${CHECKBOX_ID}--message`); + expect(wrapper.find(Message)).toHaveProp('role', 'alert'); }); it('applies no props to any other element', () => { diff --git a/packages/menus/src/containers/MenuContainer.js b/packages/menus/src/containers/MenuContainer.js index f2b65bf101d..5f016f8b158 100644 --- a/packages/menus/src/containers/MenuContainer.js +++ b/packages/menus/src/containers/MenuContainer.js @@ -212,8 +212,6 @@ class MenuContainer extends ControlledComponent { } }; - getMenuId = () => `${this.getControlledState().id}--container`; - toggleMenuVisibility = ({ defaultFocusedIndex, focusOnOpen, closedByBlur } = {}) => { const { isOpen } = this.getControlledState(); @@ -235,7 +233,6 @@ class MenuContainer extends ControlledComponent { return { tabIndex, 'aria-haspopup': true, - 'aria-controls': this.getMenuId(), 'aria-expanded': isOpen, onClick: composeEventHandlers(onClick, () => this.toggleMenuVisibility()), onKeyDown: composeEventHandlers(onKeyDown, event => { @@ -285,13 +282,12 @@ class MenuContainer extends ControlledComponent { * Props to be applied to the menu container */ getMenuProps = ( - { id = this.getMenuId(), tabIndex = -1, role = 'menu', onKeyDown, onFocus, ...other } = {}, + { tabIndex = -1, role = 'menu', onKeyDown, onFocus, ...other } = {}, focusSelectionModel ) => { const { focusOnOpen } = this.getControlledState(); return { - id, role, tabIndex, onFocus: composeEventHandlers(onFocus, event => { @@ -382,7 +378,7 @@ class MenuContainer extends ControlledComponent { /** * Props to be applied to each selectable menu item **/ - getItemProps = ({ key, textValue, ...other }) => { + getItemProps = ({ key, role = 'menuitemcheckbox', textValue, ...other }) => { const { focusedKey } = this.getControlledState(); if (typeof textValue === 'string' && textValue.length > 0) { @@ -409,6 +405,7 @@ class MenuContainer extends ControlledComponent { return { key, + role, ...other }; }; @@ -558,7 +555,10 @@ class MenuContainer extends ControlledComponent { getFocusJailContainerProps( getContainerProps(this.getMenuProps(props, focusSelectionModel)) ), - getItemProps: props => getSelectionItemProps(this.getItemProps(props)), + getItemProps: props => + getSelectionItemProps(this.getItemProps(props), { + selectedAriaKey: 'aria-checked' + }), getNextItemProps: props => getSelectionItemProps( this.getItemProps(this.getNextItemProps(props)) diff --git a/packages/menus/src/containers/MenuContainer.spec.js b/packages/menus/src/containers/MenuContainer.spec.js index 518d0efa849..5e937411cba 100644 --- a/packages/menus/src/containers/MenuContainer.spec.js +++ b/packages/menus/src/containers/MenuContainer.spec.js @@ -260,7 +260,6 @@ describe('MenuContainer', () => { expect(trigger).toHaveProp('tabIndex', 0); expect(trigger).toHaveProp('aria-haspopup', true); - expect(trigger).toHaveProp('aria-controls'); expect(trigger).toHaveProp('aria-expanded', false); }); @@ -446,6 +445,12 @@ describe('MenuContainer', () => { }); }); + describe('getItemProps', () => { + it('applies correct accessibility role', () => { + expect(findMenuItems(wrapper).first()).toHaveProp('role', 'menuitemcheckbox'); + }); + }); + describe('getNextItemProps()', () => { beforeEach(() => { wrapper = mountWithTheme(basicExample({ onChange: onChangeSpy })); diff --git a/packages/pagination/src/containers/PaginationContainer.js b/packages/pagination/src/containers/PaginationContainer.js index a7a308a3c7a..8737bf498f3 100644 --- a/packages/pagination/src/containers/PaginationContainer.js +++ b/packages/pagination/src/containers/PaginationContainer.js @@ -51,10 +51,9 @@ export default class PaginationContainer extends ControlledComponent { }; } - getContainerProps = ({ role = 'navigation', ...otherProps } = {}) => { + getContainerProps = ({ ...otherProps } = {}) => { return { - role, - 'aria-label': 'Pagination', + 'aria-label': 'Pagination navigation', ...otherProps }; }; diff --git a/packages/pagination/src/containers/PaginationContainer.spec.js b/packages/pagination/src/containers/PaginationContainer.spec.js index 9e0d7d1ca8f..44630829ce1 100644 --- a/packages/pagination/src/containers/PaginationContainer.spec.js +++ b/packages/pagination/src/containers/PaginationContainer.spec.js @@ -73,8 +73,8 @@ describe('PaginationContainer', () => { it('applies correct accessibility attributes', () => { const container = findContainer(wrapper); - expect(container).toHaveProp('role', 'navigation'); - expect(container).toHaveProp('aria-label', 'Pagination'); + expect(container).toHaveProp('role', 'listbox'); + expect(container).toHaveProp('aria-label', 'Pagination navigation'); }); }); diff --git a/packages/radios/src/elements/Radio.js b/packages/radios/src/elements/Radio.js index 19cfe44a295..73f4bb89fbd 100644 --- a/packages/radios/src/elements/Radio.js +++ b/packages/radios/src/elements/Radio.js @@ -65,9 +65,17 @@ export default class Radio extends ControlledComponent { */ const { onMouseDown: onFocusMouseDown, ...checkboxProps } = getFocusProps(checkboxInputProps); + let isDescribed = false; + + Children.forEach(children, child => { + if (hasType(child, Hint)) { + isDescribed = true; + } + }); + return ( - + {Children.map(children, child => { if (!isValidElement(child)) { return child; diff --git a/packages/radios/src/elements/Radio.spec.js b/packages/radios/src/elements/Radio.spec.js index f7363dc9ae3..c9ea4980bd5 100644 --- a/packages/radios/src/elements/Radio.spec.js +++ b/packages/radios/src/elements/Radio.spec.js @@ -42,7 +42,7 @@ describe('Radio', () => { }); it('applies container props to Message', () => { - expect(wrapper.find(Message)).toHaveProp('id', `${RADIO_ID}--message`); + expect(wrapper.find(Message)).toHaveProp('role', 'alert'); }); it('applies no props to any other element', () => { diff --git a/packages/ranges/src/elements/RangeField.js b/packages/ranges/src/elements/RangeField.js index 541fc63a906..42b12c606a2 100644 --- a/packages/ranges/src/elements/RangeField.js +++ b/packages/ranges/src/elements/RangeField.js @@ -42,6 +42,14 @@ export default class RangeField extends ControlledComponent { const { id } = this.getControlledState(); const { children, ...otherProps } = this.props; + let isDescribed = false; + + Children.forEach(children, child => { + if (hasType(child, Hint)) { + isDescribed = true; + } + }); + return ( {({ getLabelProps, getInputProps, getHintProps, getMessageProps }) => ( @@ -56,7 +64,7 @@ export default class RangeField extends ControlledComponent { } if (hasType(child, Range)) { - return cloneElement(child, getInputProps(child.props)); + return cloneElement(child, getInputProps(child.props, { isDescribed })); } if (hasType(child, Hint)) { diff --git a/packages/ranges/src/elements/RangeField.spec.js b/packages/ranges/src/elements/RangeField.spec.js index 0960a7b3ff4..f05b7b046da 100644 --- a/packages/ranges/src/elements/RangeField.spec.js +++ b/packages/ranges/src/elements/RangeField.spec.js @@ -43,7 +43,7 @@ describe('RangeField', () => { }); it('applies container props to Message', () => { - expect(wrapper.find(Message)).toHaveProp('id', `${RANGE_FIELD_ID}--message`); + expect(wrapper.find(Message)).toHaveProp('role', 'alert'); }); it('applies input props to Range', () => { diff --git a/packages/select/src/containers/SelectContainer.js b/packages/select/src/containers/SelectContainer.js index 3c0990aedd5..def874a13e7 100644 --- a/packages/select/src/containers/SelectContainer.js +++ b/packages/select/src/containers/SelectContainer.js @@ -119,20 +119,6 @@ export default class SelectContainer extends ControlledComponent { }; } - getTriggerProps = ({ role = 'combobox', ...other } = {}) => { - return { - role, - ...other - }; - }; - - getSelectProps = ({ role = 'listbox', ...other } = {}) => { - return { - role, - ...other - }; - }; - render() { const { children, @@ -163,7 +149,7 @@ export default class SelectContainer extends ControlledComponent { trigger={({ getTriggerProps: getMenuTriggerProps, triggerRef }) => trigger && trigger({ - getTriggerProps: props => getMenuTriggerProps(this.getTriggerProps(props)), + getTriggerProps: getMenuTriggerProps, triggerRef, selectedKey, isOpen @@ -183,7 +169,7 @@ export default class SelectContainer extends ControlledComponent { scheduleUpdate }) => render({ - getSelectProps: props => getMenuProps(this.getSelectProps(props)), + getSelectProps: getMenuProps, getItemProps, getNextItemProps, getPreviousItemProps, diff --git a/packages/select/src/containers/SelectContainer.spec.js b/packages/select/src/containers/SelectContainer.spec.js index 43964c76c60..30c5f9d2732 100644 --- a/packages/select/src/containers/SelectContainer.spec.js +++ b/packages/select/src/containers/SelectContainer.spec.js @@ -61,23 +61,8 @@ describe('SelectContainer', () => { }); const findTrigger = enzymeWrapper => enzymeWrapper.find('[data-test-id="trigger"]'); - const findDropdown = enzymeWrapper => enzymeWrapper.find('[data-test-id="dropdown"]'); const findItems = enzymeWrapper => enzymeWrapper.find('[data-test-id="item"]'); - describe('getTriggerProps()', () => { - it('applies correct accessibility attributes', () => { - expect(findTrigger(wrapper)).toHaveProp('role', 'combobox'); - }); - }); - - describe('getSelectProps()', () => { - it('applies correct accessibility attributes', () => { - findTrigger(wrapper).simulate('click'); - wrapper.update(); - expect(findDropdown(wrapper)).toHaveProp('role', 'listbox'); - }); - }); - describe('onChange', () => { it('receives selected item key on select', () => { findTrigger(wrapper).simulate('click'); diff --git a/packages/select/src/elements/SelectField.js b/packages/select/src/elements/SelectField.js index 901b91797f9..cc777f39def 100644 --- a/packages/select/src/elements/SelectField.js +++ b/packages/select/src/elements/SelectField.js @@ -57,6 +57,14 @@ export default class SelectField extends ControlledComponent { this.selectRef = undefined; + let isDescribed = false; + + Children.forEach(children, child => { + if (hasType(child, Hint)) { + isDescribed = true; + } + }); + return ( @@ -84,7 +92,10 @@ export default class SelectField extends ControlledComponent { } if (hasType(child, Select)) { - return cloneElement(child, getFieldInputProps(this.getInputProps(child.props))); + return cloneElement( + child, + getFieldInputProps(this.getInputProps(child.props), { isDescribed }) + ); } return child; diff --git a/packages/selection/src/containers/FieldContainer.js b/packages/selection/src/containers/FieldContainer.js index 698ac1c217f..934131a344f 100644 --- a/packages/selection/src/containers/FieldContainer.js +++ b/packages/selection/src/containers/FieldContainer.js @@ -43,8 +43,6 @@ export default class FieldContainer extends ControlledComponent { retrieveHintId = () => `${this.getControlledState().id}--hint`; - retrieveMessageId = () => `${this.getControlledState().id}--message`; - getLabelProps = ({ id = this.retrieveLabelId(), htmlFor = this.retrieveInputId(), @@ -57,11 +55,11 @@ export default class FieldContainer extends ControlledComponent { }; }; - getInputProps = ({ id = this.retrieveInputId(), ...other } = {}) => { + getInputProps = ({ id = this.retrieveInputId(), ...other } = {}, { isDescribed = true } = {}) => { return { id, 'aria-labelledby': this.retrieveLabelId(), - 'aria-describedby': `${this.retrieveHintId()} ${this.retrieveMessageId()}`, + 'aria-describedby': isDescribed ? this.retrieveHintId() : null, ...other }; }; @@ -73,9 +71,9 @@ export default class FieldContainer extends ControlledComponent { }; }; - getMessageProps = ({ id = this.retrieveMessageId(), ...other } = {}) => { + getMessageProps = ({ role = 'alert', ...other } = {}) => { return { - id, + role, ...other }; }; diff --git a/packages/selection/src/containers/FieldContainer.spec.js b/packages/selection/src/containers/FieldContainer.spec.js index 1e963a5a2e0..1cccff22ec4 100644 --- a/packages/selection/src/containers/FieldContainer.spec.js +++ b/packages/selection/src/containers/FieldContainer.spec.js @@ -51,15 +51,26 @@ describe('FieldContainer', () => { }); describe('getInputProps', () => { - it('applies correct accessibility role', () => { + it('applies correct accessibility attributes', () => { const input = findInput(wrapper); expect(input).toHaveProp('id', `${CONTAINER_ID}--input`); expect(input).toHaveProp('aria-labelledby', `${CONTAINER_ID}--label`); - expect(input).toHaveProp( - 'aria-describedby', - `${CONTAINER_ID}--hint ${CONTAINER_ID}--message` + expect(input).toHaveProp('aria-describedby', `${CONTAINER_ID}--hint`); + }); + + it('excludes aria-describedby if option is provided', () => { + wrapper = mount( + + {({ getInputProps }) => ( +
+ +
+ )} +
); + + expect(findInput(wrapper)).toHaveProp('aria-describedby', null); }); }); @@ -71,7 +82,7 @@ describe('FieldContainer', () => { describe('getMessageProps', () => { it('applies correct accessibility role', () => { - expect(findMessage(wrapper)).toHaveProp('id', `${CONTAINER_ID}--message`); + expect(findMessage(wrapper)).toHaveProp('role', 'alert'); }); }); }); diff --git a/packages/selection/src/containers/SelectionContainer.js b/packages/selection/src/containers/SelectionContainer.js index d931f0e0530..b6150c8019c 100644 --- a/packages/selection/src/containers/SelectionContainer.js +++ b/packages/selection/src/containers/SelectionContainer.js @@ -238,9 +238,12 @@ export class SelectionContainer extends ControlledComponent { }; getItemId = key => - typeof key === 'undefined' ? '' : `${this.getControlledState().id}--item-${key}`; + typeof key === 'undefined' ? null : `${this.getControlledState().id}--item-${key}`; - getItemProps = ({ key, id = this.getItemId(key), role = 'option', onClick, ...props } = {}) => { + getItemProps = ( + { key, id = this.getItemId(key), role = 'option', onClick, ...props } = {}, + { selectedAriaKey = 'aria-selected' } = {} + ) => { if (typeof key === 'undefined') { throw new Error( '"key" must be defined within getItemProps regardless of being used within a .map()' @@ -266,7 +269,7 @@ export class SelectionContainer extends ControlledComponent { id, key, role, - 'aria-selected': isSelectedItem, + [selectedAriaKey]: isSelectedItem, onClick: composeEventHandlers(onClick, () => { this.selectItem(key, undefined); }), diff --git a/packages/selection/src/containers/SelectionContainer.spec.js b/packages/selection/src/containers/SelectionContainer.spec.js index 08e16b29ea6..2cb7eba11de 100644 --- a/packages/selection/src/containers/SelectionContainer.spec.js +++ b/packages/selection/src/containers/SelectionContainer.spec.js @@ -16,7 +16,7 @@ describe('SelectionContainer', () => { const itemValues = ['Item-1', 'Item-2', 'Item-3']; let wrapper; - const basicExample = (direction, defaultFocusedIndex) => ( + const basicExample = ({ direction, defaultFocusedIndex, selectedAriaKey } = {}) => ( {
{itemValues.map(item => (
{item}
@@ -87,7 +92,7 @@ describe('SelectionContainer', () => { }); it('focuses last item if no item is currently selected and defaultFocusedIndex is provided', () => { - wrapper = mountWithTheme(basicExample(undefined, -1)); + wrapper = mountWithTheme(basicExample({ defaultFocusedIndex: -1 })); findContainer(wrapper).simulate('focus'); @@ -295,7 +300,7 @@ describe('SelectionContainer', () => { describe('while using vertical direction', () => { beforeEach(() => { - wrapper = mountWithTheme(basicExample('vertical')); + wrapper = mountWithTheme(basicExample({ direction: 'vertical' })); }); describe('UP keyCode', () => { @@ -406,6 +411,15 @@ describe('SelectionContainer', () => { expect(findItems(wrapper).first()).toHaveProp('role', 'option'); }); + it('applies default selected aria value if none provided', () => { + expect(findItems(wrapper).first()).toHaveProp('aria-selected'); + }); + + it('applies custom selected aria value if provided', () => { + wrapper = mountWithTheme(basicExample({ selectedAriaKey: 'aria-pressed' })); + expect(findItems(wrapper).first()).toHaveProp('aria-pressed'); + }); + describe('onClick', () => { it('should select item that was clicked', () => { findItems(wrapper) diff --git a/packages/textfields/src/elements/TextField.js b/packages/textfields/src/elements/TextField.js index 8f659e95669..3efe622599e 100644 --- a/packages/textfields/src/elements/TextField.js +++ b/packages/textfields/src/elements/TextField.js @@ -43,6 +43,14 @@ export default class TextField extends ControlledComponent { const { id } = this.getControlledState(); const { children, ...otherProps } = this.props; + let isDescribed = false; + + Children.forEach(children, child => { + if (hasType(child, Hint)) { + isDescribed = true; + } + }); + return ( {({ getLabelProps, getInputProps, getHintProps, getMessageProps }) => ( @@ -57,7 +65,7 @@ export default class TextField extends ControlledComponent { } if (hasType(child, Input) || hasType(child, Textarea)) { - return cloneElement(child, getInputProps(child.props)); + return cloneElement(child, getInputProps(child.props, { isDescribed })); } if (hasType(child, Hint)) { diff --git a/packages/textfields/src/elements/TextField.spec.js b/packages/textfields/src/elements/TextField.spec.js index c9e71bbb06e..8d73c70efc3 100644 --- a/packages/textfields/src/elements/TextField.spec.js +++ b/packages/textfields/src/elements/TextField.spec.js @@ -44,7 +44,7 @@ describe('TextField', () => { }); it('applies container props to Message', () => { - expect(wrapper.find(Message)).toHaveProp('id', `${TEXT_FIELD_ID}--message`); + expect(wrapper.find(Message)).toHaveProp('role', 'alert'); }); it('applies no props to any other element', () => { diff --git a/packages/textfields/src/views/Input.js b/packages/textfields/src/views/Input.js index cbc8b9c53a5..adcd58fbd5c 100644 --- a/packages/textfields/src/views/Input.js +++ b/packages/textfields/src/views/Input.js @@ -18,12 +18,17 @@ const VALIDATION = { ERROR: 'error' }; +const isInvalid = validation => { + return validation === VALIDATION.WARNING || validation === VALIDATION.ERROR; +}; + /** * Accepts all `` props */ const Input = styled.input.attrs({ 'data-garden-id': COMPONENT_ID, 'data-garden-version': PACKAGE_VERSION, + 'aria-invalid': props => isInvalid(props.validation), className: props => classNames(TextStyles['c-txt__input'], { [TextStyles['c-txt__input--sm']]: props.small, diff --git a/packages/textfields/src/views/__snapshots__/Input.spec.js.snap b/packages/textfields/src/views/__snapshots__/Input.spec.js.snap index 5d806afe44d..31aa32106db 100644 --- a/packages/textfields/src/views/__snapshots__/Input.spec.js.snap +++ b/packages/textfields/src/views/__snapshots__/Input.spec.js.snap @@ -2,6 +2,7 @@ exports[`Input renders RTL styling 1`] = `