diff --git a/packages/mdc-textfield/README.md b/packages/mdc-textfield/README.md index 3cd260c66bd..0163dbdfe56 100644 --- a/packages/mdc-textfield/README.md +++ b/packages/mdc-textfield/README.md @@ -100,54 +100,6 @@ since it won't be added until that JS runs, adding it manually will prevent an i ``` -### Using helper text - -MDC Text Fields can include helper text that is useful for providing supplemental -information to users, as well for validation messages (covered below). - -```html -
- We will never share your email address with third parties -
-``` - -#### Helper text and accessibility - -Note that in every example where the helper text is dependent on the state of the input element, we -assign an id to the `mdc-text-field-helper-text` element and set that id to the value of the -`aria-controls` attribute on the input element. We recommend doing this as well as it will help -indicate to assistive devices that the display of the helper text is dependent on the interaction with -the input element. - -When using our vanilla JS component, if it sees that the input element has an `aria-controls` -attribute, it will look for an element with the id specified and treat it as the text field's help -text element, taking care of adding/removing `aria-hidden` and other a11y attributes. This can also -be done programmatically, which is described below. - ### Validation MDC TextField provides validity styling by using the `:invalid` and `:required` attributes provided @@ -162,29 +114,9 @@ by HTML5's form validation API. ``` By default an input's validity is checked via `checkValidity()` on blur, and the styles are updated -accordingly. Set the MDCTextField.valid variable to set the input's validity explicitly. MDC TextField +accordingly. Set the MDCTextField.valid field to set the input's validity explicitly. MDC TextField automatically appends an asterisk to the label text if the required attribute is set. -Helper text can be used to provide additional validation messages. Use -`mdc-text-field-helper-text--validation-msg` to provide styles for using the helper text as a validation -message. This can be easily combined with `mdc-text-field-helper-text--persistent` to provide a robust -UX for client-side form field validation. - -```html -- Must be at least 8 characters long -
-``` - ### Leading and Trailing Icons Leading and trailing icons can be added to MDC Text Fields as visual indicators as well as interaction targets. To do so, add the relevant classes @@ -267,7 +199,7 @@ behave normally. ``` -Note that Text field boxes support all of the same features as normal text-fields, including help +Note that Text field boxes support all of the same features as normal text-fields, including helper text, validation, and dense UI. #### CSS-only text field boxes @@ -366,17 +298,6 @@ By default the ripple factory simply calls `new MDCRipple(el)` and returns the r Similar to regular DOM elements, the `MDCTextField` functionality is exposed through accessor methods. -##### MDCTextField.helperTextElement - -HTMLLabelElement. This is a normal property (non-accessor) that holds a reference to the element -being used as the text field's "helper text". It defaults to `null`. If the text field's input element -contains an `aria-controls` attribute on instantiation of the component, it will look for an element -with the corresponding id within the document and automatically assign it to this property. - -##### MDCTextField.helperTextContent - -String setter. Proxies to the foundation's `setHelperTextContent` method when set. - ##### MDCTextField.disabled Boolean. Proxies to the foundation's `isDisabled/setDisabled` methods when retrieved/set @@ -408,18 +329,13 @@ complicated. | notifyIconAction() => void | Emits a custom event "MDCTextField:icon" denoting a user has clicked the icon | | addClassToBottomLine(className: string) => void | Adds a class to the bottom line element | | removeClassFromBottomLine(className: string) => void | Removes a class from the bottom line element | -| addClassToHelperText(className: string) => void | Adds a class to the helper text element. Note that in our code we check for whether or not we have a helper text element and if we don't, we simply return. | -| removeClassFromHelperText(className: string) => void | Removes a class from the helper text element. | -| helperTextHasClass(className: string) => boolean | Returns whether or not the helper text element contains the current class | | registerInputInteractionHandler(evtType: string, handler: EventListener) => void | Registers an event listener on the native input element for a given event | | deregisterInputInteractionHandler(evtType: string, handler: EventListener) => void | Deregisters an event listener on the native input element for a given event | | registerBottomLineEventHandler(evtType: string, handler: EventListener) => void | Registers an event listener on the bottom line element for a given event | | deregisterBottomLineEventHandler(evtType: string, handler: EventListener) => void | Deregisters an event listener on the bottom line element for a given event | -| setHelperTextAttr(name: string, value: string) => void | Sets an attribute with a given value on the helper text element | -| removeHelperTextAttr(name: string) => void | Removes an attribute from the helper text element | -| setHelperTextContent(content: string) => void | Sets the content of the helper text element | | getNativeInput() => {value: string, disabled: boolean, badInput: boolean, checkValidity: () => boolean}? | Returns an object representing the native text input element, with a similar API shape. The object returned should include the `value`, `disabled` and `badInput` properties, as well as the `checkValidity()` function. We _never_ alter the value within our code, however we _do_ update the disabled property, so if you choose to duck-type the return value for this method in your implementation it's important to keep this in mind. Also note that this method can return null, which the foundation will handle gracefully. | | getBottomLineFoundation() => MDCTextFieldBottomLineFoundation | Returns the instance of the bottom line element's foundation | +| getHelperTextFoundation() => MDCTextFieldHelperTextFoundation | Returns the instance of the helper text element's foundation | #### The full foundation API @@ -457,6 +373,10 @@ event with clientX/clientY properties. Handles the end of the bottom line animation, performing actions that must wait for animations to finish. Expects a transition-end event. +##### MDCTextField.helperTextContent + +Sets the content of the helper text, if it exists. + ### Theming MDC TextField components use the configured theme's primary color for its underline and label text diff --git a/packages/mdc-textfield/adapter.js b/packages/mdc-textfield/adapter.js index 88de5fba68a..ca4ffdc0415 100644 --- a/packages/mdc-textfield/adapter.js +++ b/packages/mdc-textfield/adapter.js @@ -17,6 +17,7 @@ /* eslint-disable no-unused-vars */ import MDCTextFieldBottomLineFoundation from './bottom-line/foundation'; +import MDCTextFieldHelperTextFoundation from './helper-text/foundation'; /* eslint no-unused-vars: [2, {"args": "none"}] */ @@ -188,6 +189,13 @@ class MDCTextFieldAdapter { * @return {?MDCTextFieldBottomLineFoundation} */ getBottomLineFoundation() {} + + /** + * Returns the foundation for the helper text element. Returns undefined if + * there is no helper text element. + * @return {?MDCTextFieldHelperTextFoundation} + */ + getHelperTextFoundation() {} } export {MDCTextFieldAdapter, NativeInputType}; diff --git a/packages/mdc-textfield/constants.js b/packages/mdc-textfield/constants.js index 5e2f05ee1cd..3d564b97090 100644 --- a/packages/mdc-textfield/constants.js +++ b/packages/mdc-textfield/constants.js @@ -17,8 +17,7 @@ /** @enum {string} */ const strings = { - ARIA_HIDDEN: 'aria-hidden', - ROLE: 'role', + ARIA_CONTROLS: 'aria-controls', INPUT_SELECTOR: '.mdc-text-field__input', LABEL_SELECTOR: '.mdc-text-field__label', ICON_SELECTOR: '.mdc-text-field__icon', @@ -33,8 +32,6 @@ const cssClasses = { DISABLED: 'mdc-text-field--disabled', FOCUSED: 'mdc-text-field--focused', INVALID: 'mdc-text-field--invalid', - HELPER_TEXT_PERSISTENT: 'mdc-text-field-helper-text--persistent', - HELPER_TEXT_VALIDATION_MSG: 'mdc-text-field-helper-text--validation-msg', LABEL_FLOAT_ABOVE: 'mdc-text-field__label--float-above', LABEL_SHAKE: 'mdc-text-field__label--shake', BOX: 'mdc-text-field--box', diff --git a/packages/mdc-textfield/foundation.js b/packages/mdc-textfield/foundation.js index ef5e8f0f977..3fb2dfc3cc3 100644 --- a/packages/mdc-textfield/foundation.js +++ b/packages/mdc-textfield/foundation.js @@ -52,18 +52,13 @@ class MDCTextFieldFoundation extends MDCFoundation { registerTextFieldInteractionHandler: () => {}, deregisterTextFieldInteractionHandler: () => {}, notifyIconAction: () => {}, - addClassToHelperText: () => {}, - removeClassFromHelperText: () => {}, - helperTextHasClass: () => false, registerInputInteractionHandler: () => {}, deregisterInputInteractionHandler: () => {}, registerBottomLineEventHandler: () => {}, deregisterBottomLineEventHandler: () => {}, - setHelperTextAttr: () => {}, - removeHelperTextAttr: () => {}, - setHelperTextContent: () => {}, getNativeInput: () => {}, getBottomLineFoundation: () => {}, + getHelperTextFoundation: () => {}, }); } @@ -161,7 +156,10 @@ class MDCTextFieldFoundation extends MDCFoundation { } this.adapter_.addClassToLabel(LABEL_FLOAT_ABOVE); this.adapter_.removeClassFromLabel(LABEL_SHAKE); - this.showHelperText_(); + const helperText = this.adapter_.getHelperTextFoundation(); + if (helperText) { + helperText.showToScreenReader(); + } this.isFocused_ = true; } @@ -187,15 +185,6 @@ class MDCTextFieldFoundation extends MDCFoundation { } } - /** - * Makes the helper text visible to screen readers. - * @private - */ - showHelperText_() { - const {ARIA_HIDDEN} = MDCTextFieldFoundation.strings; - this.adapter_.removeHelperTextAttr(ARIA_HIDDEN); - } - /** * Handles when bottom line animation ends, performing actions that must wait * for animations to finish. @@ -244,40 +233,10 @@ class MDCTextFieldFoundation extends MDCFoundation { this.adapter_.addClassToLabel(LABEL_SHAKE); this.adapter_.addClass(INVALID); } - this.updateHelperText_(isValid); - } - - /** - * Updates the state of the Text Field's helper text based on validity and - * the Text Field's options. - * @param {boolean} isValid - */ - updateHelperText_(isValid) { - const {HELPER_TEXT_PERSISTENT, HELPER_TEXT_VALIDATION_MSG} = MDCTextFieldFoundation.cssClasses; - const {ROLE} = MDCTextFieldFoundation.strings; - const helperTextIsPersistent = this.adapter_.helperTextHasClass(HELPER_TEXT_PERSISTENT); - const helperTextIsValidationMsg = this.adapter_.helperTextHasClass(HELPER_TEXT_VALIDATION_MSG); - const validationMsgNeedsDisplay = helperTextIsValidationMsg && !isValid; - - if (validationMsgNeedsDisplay) { - this.adapter_.setHelperTextAttr(ROLE, 'alert'); - } else { - this.adapter_.removeHelperTextAttr(ROLE); - } - - if (helperTextIsPersistent || validationMsgNeedsDisplay) { - return; + const helperText = this.adapter_.getHelperTextFoundation(); + if (helperText) { + helperText.setValidity(isValid); } - this.hideHelperText_(); - } - - /** - * Hides the helper text from screen readers. - * @private - */ - hideHelperText_() { - const {ARIA_HIDDEN} = MDCTextFieldFoundation.strings; - this.adapter_.setHelperTextAttr(ARIA_HIDDEN, 'true'); } /** @@ -313,10 +272,13 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * @param {string} content Sets the content of the helper text field + * @param {string} content Sets the content of the helper text. */ setHelperTextContent(content) { - this.adapter_.setHelperTextContent(content); + const helperText = this.adapter_.getHelperTextFoundation(); + if (helperText) { + helperText.setContent(content); + } } /** diff --git a/packages/mdc-textfield/helper-text/README.md b/packages/mdc-textfield/helper-text/README.md new file mode 100644 index 00000000000..80dcc8c3992 --- /dev/null +++ b/packages/mdc-textfield/helper-text/README.md @@ -0,0 +1,134 @@ + + +# Text Field Helper Text + +The helper text provides supplemental information and/or validation messages to users. It appears on input field focus and disappears on input field blur by default, or it can be persistent. + +## Design & API Documentation + + + + +## Usage + +### Using helper text + +MDC Text Fields can include helper text that is useful for providing supplemental information to users. + +```html ++ We will never share your email address with third parties +
+``` + +#### Helper text and accessibility + +Note that in every example where the helper text is dependent on the state of the input element, we +assign an id to the `mdc-text-field-helper-text` element and set that id to the value of the +`aria-controls` attribute on the input element. We recommend doing this as well as it will help +indicate to assistive devices that the display of the helper text is dependent on the interaction with +the input element. + +```html ++ Must be at least 8 characters long +
+``` + +#### MDCTextFieldHelperText API + +##### MDCTextFieldHelperText.foundation + +MDCTextFieldHelperTextFoundation. This allows the parent MDCTextField component to access the public methods on the MDCTextFieldHelperTextFoundation class. + +### Using the foundation class + +Method Signature | Description +--- | --- +addClass(className: string) => void | Adds a class to the helper text element +removeClass(className: string) => void | Removes a class from the helper text element +hasClass(className: string) => boolean | Returns true if classname exists for the helper text element +setAttr(attr: string, value: string) => void | Sets an attribute with a given value on the helper text element +removeAttr(attr: string) => void | Removes an attribute on the helper text element +setContent(attr: string) => void | Sets the text content for the helper text element + +#### The full foundation API + +##### MDCTextFieldHelperTextFoundation.setContent() + +Sets the content of the helper text. + +##### MDCTextFieldHelperTextFoundation.showToScreenReader() + +Makes the helper text visible to the screen reader. + +##### MDCTextFieldHelperTextFoundation.setValidity(inputIsValid) + +Sets the validity of the helper text based on the input validity. diff --git a/packages/mdc-textfield/helper-text/adapter.js b/packages/mdc-textfield/helper-text/adapter.js new file mode 100644 index 00000000000..e2072c0fe4d --- /dev/null +++ b/packages/mdc-textfield/helper-text/adapter.js @@ -0,0 +1,70 @@ +/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint no-unused-vars: [2, {"args": "none"}] */ + +/** + * Adapter for MDC Text Field Helper Text. + * + * Defines the shape of the adapter expected by the foundation. Implement this + * adapter to integrate the TextField helper text into your framework. See + * https://github.com/material-components/material-components-web/blob/master/docs/authoring-components.md + * for more information. + * + * @record + */ +class MDCTextFieldHelperTextAdapter { + /** + * Adds a class to the helper text element. + * @param {string} className + */ + addClass(className) {} + + /** + * Removes a class from the helper text element. + * @param {string} className + */ + removeClass(className) {} + + /** + * Returns whether or not the helper text element contains the given class. + * @param {string} className + * @return {boolean} + */ + hasClass(className) {} + + /** + * Sets an attribute with a given value on the helper text element. + * @param {string} attr + * @param {string} value + */ + setAttr(attr, value) {} + + /** + * Removes an attribute from the helper text element. + * @param {string} attr + */ + removeAttr(attr) {} + + /** + * Sets the text content for the helper text element. + * @param {string} content + */ + setContent(content) {} +} + +export default MDCTextFieldHelperTextAdapter; diff --git a/packages/mdc-textfield/helper-text/constants.js b/packages/mdc-textfield/helper-text/constants.js new file mode 100644 index 00000000000..8fbec6ea063 --- /dev/null +++ b/packages/mdc-textfield/helper-text/constants.js @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @enum {string} */ +const strings = { + ARIA_HIDDEN: 'aria-hidden', + ROLE: 'role', +}; + +/** @enum {string} */ +const cssClasses = { + HELPER_TEXT_PERSISTENT: 'mdc-text-field-helper-text--persistent', + HELPER_TEXT_VALIDATION_MSG: 'mdc-text-field-helper-text--validation-msg', +}; + +export {strings, cssClasses}; diff --git a/packages/mdc-textfield/helper-text/foundation.js b/packages/mdc-textfield/helper-text/foundation.js new file mode 100644 index 00000000000..a683fe00b0b --- /dev/null +++ b/packages/mdc-textfield/helper-text/foundation.js @@ -0,0 +1,103 @@ +/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MDCFoundation from '@material/base/foundation'; +import MDCTextFieldHelperTextAdapter from './adapter'; +import {cssClasses, strings} from './constants'; + + +/** + * @extends {MDCFoundation} + * @final + */ +class MDCTextFieldHelperTextFoundation extends MDCFoundation { + /** @return enum {string} */ + static get cssClasses() { + return cssClasses; + } + + /** @return enum {string} */ + static get strings() { + return strings; + } + + /** + * {@see MDCTextFieldHelperTextAdapter} for typing information on parameters and return + * types. + * @return {!MDCTextFieldHelperTextAdapter} + */ + static get defaultAdapter() { + return /** @type {!MDCTextFieldHelperTextAdapter} */ ({ + addClass: () => {}, + removeClass: () => {}, + hasClass: () => {}, + setAttr: () => {}, + removeAttr: () => {}, + setContent: () => {}, + }); + } + + /** + * @param {!MDCTextFieldHelperTextAdapter=} adapter + */ + constructor(adapter = /** @type {!MDCTextFieldHelperTextAdapter} */ ({})) { + super(Object.assign(MDCTextFieldHelperTextFoundation.defaultAdapter, adapter)); + } + + /** + * Sets the content of the helper text field. + * @param {string} content + */ + setContent(content) { + this.adapter_.setContent(content); + } + + /** Makes the helper text visible to the screen reader. */ + showToScreenReader() { + this.adapter_.removeAttr(strings.ARIA_HIDDEN); + } + + /** + * Sets the validity of the helper text based on the input validity. + * @param {boolean} inputIsValid + */ + setValidity(inputIsValid) { + const helperTextIsPersistent = this.adapter_.hasClass(cssClasses.HELPER_TEXT_PERSISTENT); + const helperTextIsValidationMsg = this.adapter_.hasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG); + const validationMsgNeedsDisplay = helperTextIsValidationMsg && !inputIsValid; + + if (validationMsgNeedsDisplay) { + this.adapter_.setAttr(strings.ROLE, 'alert'); + } else { + this.adapter_.removeAttr(strings.ROLE); + } + + if (!helperTextIsPersistent && !validationMsgNeedsDisplay) { + this.hide_(); + } + } + + /** + * Hides the help text from screen readers. + * @private + */ + hide_() { + this.adapter_.setAttr(strings.ARIA_HIDDEN, 'true'); + } +} + +export default MDCTextFieldHelperTextFoundation; diff --git a/packages/mdc-textfield/helper-text/index.js b/packages/mdc-textfield/helper-text/index.js new file mode 100644 index 00000000000..87519facf26 --- /dev/null +++ b/packages/mdc-textfield/helper-text/index.js @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MDCComponent from '@material/base/component'; + +import MDCTextFieldHelperTextAdapter from './adapter'; +import MDCTextFieldHelperTextFoundation from './foundation'; + +/** + * @extends {MDCComponent} + * @final + */ +class MDCTextFieldHelperText extends MDCComponent { + /** + * @param {!Element} root + * @return {!MDCTextFieldHelperText} + */ + static attachTo(root) { + return new MDCTextFieldHelperText(root); + } + + /** + * @return {MDCTextFieldHelperTextFoundation} + */ + get foundation() { + return this.foundation_; + } + + /** + * @return {!MDCTextFieldHelperTextFoundation} + */ + getDefaultFoundation() { + return new MDCTextFieldHelperTextFoundation(/** @type {!MDCTextFieldHelperTextAdapter} */ (Object.assign({ + addClass: (className) => this.root_.classList.add(className), + removeClass: (className) => this.root_.classList.remove(className), + hasClass: (className) => this.root_.classList.contains(className), + setAttr: (attr, value) => this.root_.setAttribute(attr, value), + removeAttr: (attr) => this.root_.removeAttribute(attr), + setContent: (content) => { + this.root_.textContent = content; + }, + }))); + } +} + +export {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation}; diff --git a/packages/mdc-textfield/helper-text/mdc-text-field-helper-text.scss b/packages/mdc-textfield/helper-text/mdc-text-field-helper-text.scss new file mode 100644 index 00000000000..9af8bbc8cb7 --- /dev/null +++ b/packages/mdc-textfield/helper-text/mdc-text-field-helper-text.scss @@ -0,0 +1,49 @@ +// +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +@import "../functions"; +@import "@material/theme/mixins"; + +// postcss-bem-linter: define text-field-helper-text + +.mdc-text-field-helper-text { + @include mdc-theme-prop(color, text-hint-on-light); + + margin: 0; + transition: mdc-text-field-transition(opacity); + opacity: 0; + font-size: .75rem; + will-change: opacity; + + @include mdc-theme-dark { + @include mdc-theme-prop(color, text-hint-on-dark); + } + + // stylelint-disable plugin/selector-bem-pattern + .mdc-text-field + & { + margin-bottom: 8px; + } + + // stylelint-enable plugin/selector-bem-pattern +} + +.mdc-text-field-helper-text--persistent { + transition: none; + opacity: 1; + will-change: initial; +} + +// postcss-bem-linter: end diff --git a/packages/mdc-textfield/index.js b/packages/mdc-textfield/index.js index a4303cc8904..c6b565f1d6f 100644 --- a/packages/mdc-textfield/index.js +++ b/packages/mdc-textfield/index.js @@ -22,6 +22,7 @@ import {cssClasses, strings} from './constants'; import {MDCTextFieldAdapter} from './adapter'; import MDCTextFieldFoundation from './foundation'; import {MDCTextFieldBottomLine} from './bottom-line'; +import {MDCTextFieldHelperText} from './helper-text'; /** * @extends {MDCComponent} @@ -37,12 +38,12 @@ class MDCTextField extends MDCComponent { this.input_; /** @private {?Element} */ this.label_; - /** @type {?Element} */ - this.helperTextElement; /** @type {?MDCRipple} */ this.ripple; /** @private {?MDCTextFieldBottomLine} */ this.bottomLine_; + /** @private {?MDCTextFieldHelperText} */ + this.helperText_; /** @private {?Element} */ this.icon_; } @@ -66,11 +67,7 @@ class MDCTextField extends MDCComponent { bottomLineFactory = (el) => new MDCTextFieldBottomLine(el)) { this.input_ = this.root_.querySelector(strings.INPUT_SELECTOR); this.label_ = this.root_.querySelector(strings.LABEL_SELECTOR); - this.helperTextElement = null; this.ripple = null; - if (this.input_.hasAttribute('aria-controls')) { - this.helperTextElement = document.getElementById(this.input_.getAttribute('aria-controls')); - } if (this.root_.classList.contains(cssClasses.BOX)) { this.ripple = rippleFactory(this.root_); }; @@ -80,6 +77,12 @@ class MDCTextField extends MDCComponent { this.bottomLine_ = bottomLineFactory(bottomLineElement); } }; + if (this.input_.hasAttribute(strings.ARIA_CONTROLS)) { + const helperTextElement = document.getElementById(this.input_.getAttribute(strings.ARIA_CONTROLS)); + if (helperTextElement) { + this.helperText_ = new MDCTextFieldHelperText(helperTextElement); + } + } if (!this.root_.classList.contains(cssClasses.TEXT_FIELD_ICON)) { this.icon_ = this.root_.querySelector(strings.ICON_SELECTOR); }; @@ -92,6 +95,9 @@ class MDCTextField extends MDCComponent { if (this.bottomLine_) { this.bottomLine_.destroy(); } + if (this.helperText_) { + this.helperText_.destroy(); + } super.destroy(); } @@ -125,7 +131,8 @@ class MDCTextField extends MDCComponent { } /** - * @param {string} content Sets the Helper Text element textContent. + * Sets the helper text element content. + * @param {string} content */ set helperTextContent(content) { this.foundation_.setHelperTextContent(content); @@ -170,9 +177,14 @@ class MDCTextField extends MDCComponent { } return undefined; }, + getHelperTextFoundation: () => { + if (this.helperText_) { + return this.helperText_.foundation; + } + return undefined; + }, }, this.getInputAdapterMethods_(), - this.getHelperTextAdapterMethods_(), this.getIconAdapterMethods_()))); } @@ -205,51 +217,6 @@ class MDCTextField extends MDCComponent { getNativeInput: () => this.input_, }; } - - /** - * @return {!{ - * addClassToHelperText: function(string): undefined, - * removeClassFromHelperText: function(string): undefined, - * helperTextHasClass: function(string): boolean, - * setHelperTextAttr: function(string, string): undefined, - * removeHelperTextAttr: function(string): undefined, - * }} - */ - getHelperTextAdapterMethods_() { - return { - addClassToHelperText: (className) => { - if (this.helperTextElement) { - this.helperTextElement.classList.add(className); - } - }, - removeClassFromHelperText: (className) => { - if (this.helperTextElement) { - this.helperTextElement.classList.remove(className); - } - }, - helperTextHasClass: (className) => { - if (!this.helperTextElement) { - return false; - } - return this.helperTextElement.classList.contains(className); - }, - setHelperTextAttr: (name, value) => { - if (this.helperTextElement) { - this.helperTextElement.setAttribute(name, value); - } - }, - removeHelperTextAttr: (name) => { - if (this.helperTextElement) { - this.helperTextElement.removeAttribute(name); - } - }, - setHelperTextContent: (content) => { - if (this.helperTextElement) { - this.helperTextElement.textContent = content; - } - }, - }; - } } export {MDCTextField, MDCTextFieldFoundation}; diff --git a/packages/mdc-textfield/mdc-text-field.scss b/packages/mdc-textfield/mdc-text-field.scss index 7ffd627f338..29963da62b2 100644 --- a/packages/mdc-textfield/mdc-text-field.scss +++ b/packages/mdc-textfield/mdc-text-field.scss @@ -25,10 +25,12 @@ @import "@material/typography/mixins"; @import "@material/typography/variables"; @import "./bottom-line/mdc-text-field-bottom-line"; +@import "./helper-text/mdc-text-field-helper-text"; @include mdc-text-field-invalid-label-shake_keyframes_(standard, 100%); @include mdc-text-field-invalid-label-shake_keyframes_(box, 50%); // postcss-bem-linter: define text-field + .mdc-text-field { display: inline-block; position: relative; @@ -617,23 +619,7 @@ // postcss-bem-linter: define text-field-helper-text .mdc-text-field-helper-text { - @include mdc-theme-prop(color, text-hint-on-light); - - margin: 0; - transition: mdc-text-field-transition(opacity); - opacity: 0; - font-size: .75rem; - will-change: opacity; - - @include mdc-theme-dark { - @include mdc-theme-prop(color, text-hint-on-dark); - } - // stylelint-disable plugin/selector-bem-pattern - .mdc-text-field + & { - margin-bottom: 8px; - } - .mdc-text-field--dense + & { margin-bottom: 4px; } @@ -650,12 +636,6 @@ // stylelint-enable plugin/selector-bem-pattern } -.mdc-text-field-helper-text--persistent { - transition: none; - opacity: 1; - will-change: initial; -} - // postcss-bem-linter: end .mdc-text-field--invalid { diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index 50c60f36e72..750600ffe7c 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -39,11 +39,9 @@ test('defaultAdapter returns a complete adapter implementation', () => { 'addClass', 'removeClass', 'addClassToLabel', 'removeClassFromLabel', 'setIconAttr', 'eventTargetHasClass', 'registerTextFieldInteractionHandler', 'deregisterTextFieldInteractionHandler', 'notifyIconAction', - 'addClassToHelperText', 'removeClassFromHelperText', 'helperTextHasClass', 'registerInputInteractionHandler', 'deregisterInputInteractionHandler', 'registerBottomLineEventHandler', 'deregisterBottomLineEventHandler', - 'setHelperTextAttr', 'removeHelperTextAttr', 'getNativeInput', 'getBottomLineFoundation', - 'setHelperTextContent', + 'getNativeInput', 'getBottomLineFoundation', 'getHelperTextFoundation', ]); }); @@ -184,8 +182,12 @@ test('#init does not add mdc-text-field__label--float-above class if the input d test('#setHelperTextContent sets the content of the helper text element', () => { const {foundation, mockAdapter} = setupTest(); + const helperText = td.object({ + setContent: () => {}, + }); + td.when(mockAdapter.getHelperTextFoundation()).thenReturn(helperText); foundation.setHelperTextContent('foo'); - td.verify(mockAdapter.setHelperTextContent('foo')); + td.verify(mockAdapter.getHelperTextFoundation().setContent('foo')); }); test('on input focuses if input event occurs without any other events', () => { @@ -252,8 +254,12 @@ test('on focus adds mdc-text-field__label--float-above class', () => { td.verify(mockAdapter.addClassToLabel(cssClasses.LABEL_FLOAT_ABOVE)); }); -test('on focus removes aria-hidden from helperText', () => { +test('on focus makes helper text visible to the screen reader', () => { const {foundation, mockAdapter} = setupTest(); + const helperText = td.object({ + showToScreenReader: () => {}, + }); + td.when(mockAdapter.getHelperTextFoundation()).thenReturn(helperText); let focus; td.when(mockAdapter.registerInputInteractionHandler('focus', td.matchers.isA(Function))) .thenDo((evtType, handler) => { @@ -261,7 +267,7 @@ test('on focus removes aria-hidden from helperText', () => { }); foundation.init(); focus(); - td.verify(mockAdapter.removeHelperTextAttr('aria-hidden')); + td.verify(helperText.showToScreenReader()); }); const setupBlurTest = () => { @@ -331,47 +337,17 @@ test('on blur does not add mdc-textfied--invalid if custom validity is true and' td.verify(mockAdapter.addClass(cssClasses.INVALID), {times: 0}); }); -test('on blur adds role="alert" to helper text if input is invalid and helper text is being used ' + - 'as a validation message', () => { - const {mockAdapter, blur, nativeInput} = setupBlurTest(); - nativeInput.checkValidity = () => false; - td.when(mockAdapter.helperTextHasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG)).thenReturn(true); - blur(); - td.verify(mockAdapter.setHelperTextAttr('role', 'alert')); -}); - -test('on blur remove role="alert" if input is valid', () => { - const {mockAdapter, blur} = setupBlurTest(); - blur(); - td.verify(mockAdapter.removeHelperTextAttr('role')); -}); - -test('on blur sets aria-hidden="true" on helper text by default', () => { - const {mockAdapter, blur} = setupBlurTest(); - blur(); - td.verify(mockAdapter.setHelperTextAttr('aria-hidden', 'true')); -}); - -test('on blur does not set aria-hidden on helper text when it is persistent', () => { - const {mockAdapter, blur} = setupBlurTest(); - td.when(mockAdapter.helperTextHasClass(cssClasses.HELPER_TEXT_PERSISTENT)).thenReturn(true); - blur(); - td.verify(mockAdapter.setHelperTextAttr('aria-hidden', 'true'), {times: 0}); -}); - -test('on blur does not set aria-hidden if input is invalid and helper text is validation message', () => { +test('on blur set validity of helper text', () => { const {mockAdapter, blur, nativeInput} = setupBlurTest(); - td.when(mockAdapter.helperTextHasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG)).thenReturn(true); + const helperText = td.object({ + setValidity: () => {}, + hasClass: () => {}, + }); + td.when(mockAdapter.getHelperTextFoundation()).thenReturn(helperText); nativeInput.checkValidity = () => false; + td.when(helperText.hasClass('mdc-text-field-helper-text--validation-msg')).thenReturn(true); blur(); - td.verify(mockAdapter.setHelperTextAttr('aria-hidden', 'true'), {times: 0}); -}); - -test('on blur sets aria-hidden=true if input is valid and helper text is validation message', () => { - const {mockAdapter, blur} = setupBlurTest(); - td.when(mockAdapter.helperTextHasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG)).thenReturn(true); - blur(); - td.verify(mockAdapter.setHelperTextAttr('aria-hidden', 'true')); + td.verify(helperText.setValidity(false)); }); test('on blur handles getNativeInput() not returning anything gracefully', () => { diff --git a/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js new file mode 100644 index 00000000000..d2e53d299af --- /dev/null +++ b/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js @@ -0,0 +1,109 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {assert} from 'chai'; +import td from 'testdouble'; + +import {verifyDefaultAdapter} from '../helpers/foundation'; +import {setupFoundationTest} from '../helpers/setup'; +import MDCTextFieldHelperTextFoundation from '../../../packages/mdc-textfield/helper-text/foundation'; + +const {cssClasses} = MDCTextFieldHelperTextFoundation; + +suite('MDCTextFieldHelperTextFoundation'); + +test('exports cssClasses', () => { + assert.isOk('cssClasses' in MDCTextFieldHelperTextFoundation); +}); + +test('exports strings', () => { + assert.isOk('strings' in MDCTextFieldHelperTextFoundation); +}); + +test('defaultAdapter returns a complete adapter implementation', () => { + verifyDefaultAdapter(MDCTextFieldHelperTextFoundation, [ + 'addClass', 'removeClass', 'hasClass', 'setAttr', 'removeAttr', 'setContent', + ]); +}); + +const setupTest = () => setupFoundationTest(MDCTextFieldHelperTextFoundation); + +test('#setContent sets the content of the helper text element', () => { + const {foundation, mockAdapter} = setupTest(); + foundation.setContent('foo'); + td.verify(mockAdapter.setContent('foo')); +}); + +test('#showToScreenReader removes aria-hidden from helperText', () => { + const {foundation, mockAdapter} = setupTest(); + foundation.showToScreenReader(); + td.verify(mockAdapter.removeAttr('aria-hidden')); +}); + +test('#setValidity adds role="alert" to helper text if input is invalid and helper text is being used ' + + 'as a validation message', () => { + const {foundation, mockAdapter} = setupTest(); + const inputIsValid = false; + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_PERSISTENT)).thenReturn(false); + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG)).thenReturn(true); + foundation.setValidity(inputIsValid); + td.verify(mockAdapter.setAttr('role', 'alert')); +}); + +test('#setValidity removes role="alert" if input is valid', () => { + const {foundation, mockAdapter} = setupTest(); + const inputIsValid = true; + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_PERSISTENT)).thenReturn(false); + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG)).thenReturn(true); + foundation.setValidity(inputIsValid); + td.verify(mockAdapter.removeAttr('role')); +}); + +test('#setValidity sets aria-hidden="true" on helper text by default', () => { + const {foundation, mockAdapter} = setupTest(); + const inputIsValid = true; + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_PERSISTENT)).thenReturn(false); + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG)).thenReturn(false); + foundation.setValidity(inputIsValid); + td.verify(mockAdapter.setAttr('aria-hidden', 'true')); +}); + +test('#setValidity does not set aria-hidden on helper text when it is persistent', () => { + const {foundation, mockAdapter} = setupTest(); + const inputIsValid = true; + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_PERSISTENT)).thenReturn(true); + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG)).thenReturn(false); + foundation.setValidity(inputIsValid); + td.verify(mockAdapter.setAttr('aria-hidden', 'true'), {times: 0}); +}); + +test('#setValidity does not set aria-hidden if input is invalid and helper text is validation message', () => { + const {foundation, mockAdapter} = setupTest(); + const inputIsValid = false; + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_PERSISTENT)).thenReturn(false); + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG)).thenReturn(true); + foundation.setValidity(inputIsValid); + td.verify(mockAdapter.setAttr('aria-hidden', 'true'), {times: 0}); +}); + +test('#setValidity sets aria-hidden=true if input is valid and helper text is validation message', () => { + const {foundation, mockAdapter} = setupTest(); + const inputIsValid = true; + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_PERSISTENT)).thenReturn(false); + td.when(mockAdapter.hasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG)).thenReturn(true); + foundation.setValidity(inputIsValid); + td.verify(mockAdapter.setAttr('aria-hidden', 'true')); +}); diff --git a/test/unit/mdc-textfield/mdc-text-field-helper-text.test.js b/test/unit/mdc-textfield/mdc-text-field-helper-text.test.js new file mode 100644 index 00000000000..4290ce6c2cc --- /dev/null +++ b/test/unit/mdc-textfield/mdc-text-field-helper-text.test.js @@ -0,0 +1,76 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import bel from 'bel'; +import {assert} from 'chai'; + +import {MDCTextFieldHelperText} from '../../../packages/mdc-textfield/helper-text'; + +const getFixture = () => bel` + +`; + +suite('MDCTextFieldHelperText'); + +test('attachTo returns an MDCTextFieldHelperText instance', () => { + assert.isOk(MDCTextFieldHelperText.attachTo(getFixture()) instanceof MDCTextFieldHelperText); +}); + +function setupTest() { + const root = getFixture(); + const component = new MDCTextFieldHelperText(root); + return {root, component}; +} + +test('#adapter.addClass adds a class to the element', () => { + const {root, component} = setupTest(); + component.getDefaultFoundation().adapter_.addClass('foo'); + assert.isTrue(root.classList.contains('foo')); +}); + +test('#adapter.removeClass removes a class from the element', () => { + const {root, component} = setupTest(); + root.classList.add('foo'); + component.getDefaultFoundation().adapter_.removeClass('foo'); + assert.isFalse(root.classList.contains('foo')); +}); + +test('#adapter.hasClass returns whether or not the element contains a certain class', () => { + const {root, component} = setupTest(); + root.classList.add('foo'); + assert.isOk(component.getDefaultFoundation().adapter_.hasClass('foo')); + root.classList.remove('foo'); + assert.isNotOk(component.getDefaultFoundation().adapter_.hasClass('foo')); +}); + +test('#adapter.setAttr adds a given attribute to the element', () => { + const {root, component} = setupTest(); + component.getDefaultFoundation().adapter_.setAttr('aria-label', 'foo'); + assert.equal(root.getAttribute('aria-label'), 'foo'); +}); + +test('#adapter.removeAttr removes a given attribute from the element', () => { + const {root, component} = setupTest(); + root.setAttribute('aria-label', 'foo'); + component.getDefaultFoundation().adapter_.removeAttr('aria-label', 'foo'); + assert.isNotOk(root.hasAttribute('aria-label')); +}); + +test('#adapter.setContent sets the text content of the element', () => { + const {root, component} = setupTest(); + component.getDefaultFoundation().adapter_.setContent('foo'); + assert.equal(root.textContent, 'foo'); +}); diff --git a/test/unit/mdc-textfield/mdc-text-field.test.js b/test/unit/mdc-textfield/mdc-text-field.test.js index c4d6764d1b2..f0e55ef1b18 100644 --- a/test/unit/mdc-textfield/mdc-text-field.test.js +++ b/test/unit/mdc-textfield/mdc-text-field.test.js @@ -21,6 +21,7 @@ import {assert} from 'chai'; import {MDCRipple} from '../../../packages/mdc-ripple'; import {MDCTextField, MDCTextFieldFoundation} from '../../../packages/mdc-textfield'; +import {MDCTextFieldHelperText} from '../../../packages/mdc-textfield/helper-text/index'; const {cssClasses, strings} = MDCTextFieldFoundation; @@ -39,18 +40,6 @@ test('attachTo returns an MDCTextField instance', () => { assert.isOk(MDCTextField.attachTo(getFixture()) instanceof MDCTextField); }); -const getHelperText = () => bel`helper text
`; - -test('#constructor assigns helperTextElement to the id specified in the input aria-controls if present', () => { - const root = getFixture(); - root.querySelector('.mdc-text-field__input').setAttribute('aria-controls', 'helper-text'); - const helperText = getHelperText(); - document.body.appendChild(helperText); - const component = new MDCTextField(root); - assert.equal(component.helperTextElement, helperText); - document.body.removeChild(helperText); -}); - class FakeRipple { constructor(root) { this.root = root; @@ -86,6 +75,19 @@ test('#constructor when given a `mdc-text-field--box` element, initializes a def assert.instanceOf(component.ripple, MDCRipple); }); +const getHelperTextElement = () => bel`helper text
`; + +test('#constructor instantiates a helper text on the element with id specified in the input aria-controls' + + 'if present', () => { + const root = getFixture(); + root.querySelector('.mdc-text-field__input').setAttribute('aria-controls', 'helper-text'); + const helperText = getHelperTextElement(); + document.body.appendChild(helperText); + const component = new MDCTextField(root); + assert.instanceOf(component.helperText_, MDCTextFieldHelperText); + document.body.removeChild(helperText); +}); + test('#destroy cleans up the ripple if present', () => { const root = getFixture(); root.classList.add(cssClasses.BOX); @@ -139,18 +141,21 @@ test('set valid updates the component styles', () => { }); test('set helperTextContent updates the helper text element content', () => { - const {component} = setupTest(); - const helptext = getHelperText(); - component.helperTextElement = helptext; + const root = getFixture(); + root.querySelector('.mdc-text-field__input').setAttribute('aria-controls', 'helper-text'); + const helperText = getHelperTextElement(); + document.body.appendChild(helperText); + const component = new MDCTextField(root); component.helperTextContent = 'foo'; - assert.equal(helptext.textContent, 'foo'); + assert.equal(helperText.textContent, 'foo'); + document.body.removeChild(helperText); }); test('set helperTextContent has no effect when no helper text element is present', () => { const {component} = setupTest(); - assert.isNull(component.helperTextElement); - component.helperTextContent = 'foo'; - assert.isNull(component.helperTextElement); + assert.doesNotThrow(() => { + component.helperTextContent = 'foo'; + }); }); test('#adapter.setIconAttr sets a given attribute to a given value to the icon element', () => { @@ -258,87 +263,6 @@ test('#adapter.getNativeInput returns the component input element', () => { ); }); -test('#adapter.addClassToHelperText does nothing if no helper text element present', () => { - const {component} = setupTest(); - assert.doesNotThrow(() => component.getDefaultFoundation().adapter_.addClassToHelperText('foo')); -}); - -test('#adapter.addClassToHelperText adds a class to the helper text element when present', () => { - const {component} = setupTest(); - component.helperTextElement = getHelperText(); - component.getDefaultFoundation().adapter_.addClassToHelperText('foo'); - assert.isOk(component.helperTextElement.classList.contains('foo')); -}); - -test('#adapter.removeClassFromHelperText does nothing if no helper text element present', () => { - const {component} = setupTest(); - assert.doesNotThrow(() => component.getDefaultFoundation().adapter_.removeClassFromHelperText('foo')); -}); - -test('#adapter.removeClassFromHelperText removes a class from the helper text element when present', () => { - const {component} = setupTest(); - const helperText = getHelperText(); - component.helperTextElement = helperText; - helperText.classList.add('foo'); - component.getDefaultFoundation().adapter_.removeClassFromHelperText('foo'); - assert.isNotOk(helperText.classList.contains('foo')); -}); - -test('#adapter.helperTextHasClass does nothing if no helper text element present', () => { - const {component} = setupTest(); - assert.doesNotThrow(() => component.getDefaultFoundation().adapter_.helperTextHasClass('foo')); -}); - -test('#adapter.helperTextHasClass returns whether or not the helper text contains a certain class', () => { - const {component} = setupTest(); - const helperText = getHelperText(); - component.helperTextElement = helperText; - helperText.classList.add('foo'); - assert.isOk(component.getDefaultFoundation().adapter_.helperTextHasClass('foo')); - helperText.classList.remove('foo'); - assert.isNotOk(component.getDefaultFoundation().adapter_.helperTextHasClass('foo')); -}); - -test('#adapter.setHelperTextAttr does nothing if no helper text element present', () => { - const {component} = setupTest(); - assert.doesNotThrow(() => component.getDefaultFoundation().adapter_.helperTextHasClass('foo')); -}); - -test('#adapter.setHelperTextAttr sets an attribute to a certain value on the helper text element', () => { - const {component} = setupTest(); - const helperText = getHelperText(); - component.helperTextElement = helperText; - component.getDefaultFoundation().adapter_.setHelperTextAttr('aria-label', 'foo'); - assert.equal(helperText.getAttribute('aria-label'), 'foo'); -}); - -test('#adapter.removeHelperTextAttr does nothing if no helper text element present', () => { - const {component} = setupTest(); - assert.doesNotThrow(() => component.getDefaultFoundation().adapter_.removeHelperTextAttr('aria-label')); -}); - -test('#adapter.removeHelperTextAttr removes an attribute on the helper text element', () => { - const {component} = setupTest(); - const helperText = getHelperText(); - helperText.setAttribute('aria-label', 'foo'); - component.helperTextElement = helperText; - component.getDefaultFoundation().adapter_.removeHelperTextAttr('aria-label'); - assert.isNotOk(helperText.hasAttribute('aria-label')); -}); - -test('#adapter.setHelperTextContent does nothing if no help text element present', () => { - const {component} = setupTest(); - assert.doesNotThrow(() => component.getDefaultFoundation().adapter_.setHelperTextContent('foo')); -}); - -test('#adapter.setHelperTextContent updates the text content of the help text element', () => { - const {component} = setupTest(); - const helptext = getHelperText(); - component.helperTextElement = helptext; - component.getDefaultFoundation().adapter_.setHelperTextContent('foo'); - assert.equal(helptext.textContent, 'foo'); -}); - test(`#adapter.notifyIconAction emits ${strings.ICON_EVENT}`, () => { const {component} = setupTest(); const handler = td.func('leadingHandler');