mirror of
https://github.com/material-components/material-web.git
synced 2024-09-11 13:46:10 +03:00
feat(switch): add required and form validity
Swapped switch's interactive element back to an `<input>` to more easily support platform validation messages. PiperOrigin-RevId: 559671594
This commit is contained in:
parent
b542d2aa12
commit
9694191ec0
@ -14,6 +14,6 @@ import {Switch} from './internal/switch.js';
|
||||
export class SwitchHarness extends Harness<Switch> {
|
||||
protected override async getInteractiveElement() {
|
||||
await this.element.updateComplete;
|
||||
return this.element.renderRoot.querySelector<HTMLElement>('.switch')!;
|
||||
return this.element.renderRoot.querySelector('input')!;
|
||||
}
|
||||
}
|
||||
|
@ -25,15 +25,15 @@ $_easing-standard: map.get($_md-sys-motion, 'easing-standard');
|
||||
|
||||
$margin: calc(var(--_track-width) - var(--_track-height));
|
||||
|
||||
.switch--selected .handle-container {
|
||||
.selected .handle-container {
|
||||
margin-inline-start: $margin;
|
||||
}
|
||||
|
||||
.switch--unselected .handle-container {
|
||||
.unselected .handle-container {
|
||||
margin-inline-end: $margin;
|
||||
}
|
||||
|
||||
.switch:disabled .handle-container {
|
||||
.disabled .handle-container {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
@ -63,12 +63,12 @@ $_easing-standard: map.get($_md-sys-motion, 'easing-standard');
|
||||
transition: background-color 67ms linear;
|
||||
}
|
||||
|
||||
.switch:disabled .handle,
|
||||
.switch:disabled .handle::before {
|
||||
.disabled .handle,
|
||||
.disabled .handle::before {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.switch--selected .handle {
|
||||
.selected .handle {
|
||||
height: var(--_selected-handle-height);
|
||||
width: var(--_selected-handle-width);
|
||||
}
|
||||
@ -78,52 +78,52 @@ $_easing-standard: map.get($_md-sys-motion, 'easing-standard');
|
||||
width: var(--_with-icon-handle-width);
|
||||
}
|
||||
|
||||
.switch--selected:enabled:active .handle,
|
||||
.switch--unselected:enabled:active .handle {
|
||||
.selected:not(.disabled):active .handle,
|
||||
.unselected:not(.disabled):active .handle {
|
||||
height: var(--_pressed-handle-height);
|
||||
width: var(--_pressed-handle-width);
|
||||
transition-timing-function: linear;
|
||||
transition-duration: 100ms;
|
||||
}
|
||||
|
||||
.switch--selected .handle::before {
|
||||
.selected .handle::before {
|
||||
background-color: var(--_selected-handle-color);
|
||||
}
|
||||
|
||||
.switch--selected:hover .handle::before {
|
||||
.selected:hover .handle::before {
|
||||
background-color: var(--_selected-hover-handle-color);
|
||||
}
|
||||
|
||||
.switch--selected:focus-within .handle::before {
|
||||
.selected:focus-within .handle::before {
|
||||
background-color: var(--_selected-focus-handle-color);
|
||||
}
|
||||
|
||||
.switch--selected:active .handle::before {
|
||||
.selected:active .handle::before {
|
||||
background-color: var(--_selected-pressed-handle-color);
|
||||
}
|
||||
|
||||
.switch--selected:disabled .handle::before {
|
||||
.selected.disabled .handle::before {
|
||||
background-color: var(--_disabled-selected-handle-color);
|
||||
opacity: var(--_disabled-selected-handle-opacity);
|
||||
}
|
||||
|
||||
.switch--unselected .handle::before {
|
||||
.unselected .handle::before {
|
||||
background-color: var(--_handle-color);
|
||||
}
|
||||
|
||||
.switch--unselected:hover .handle::before {
|
||||
.unselected:hover .handle::before {
|
||||
background-color: var(--_hover-handle-color);
|
||||
}
|
||||
|
||||
.switch--unselected:focus-within .handle::before {
|
||||
.unselected:focus-within .handle::before {
|
||||
background-color: var(--_focus-handle-color);
|
||||
}
|
||||
|
||||
.switch--unselected:active .handle::before {
|
||||
.unselected:active .handle::before {
|
||||
background-color: var(--_pressed-handle-color);
|
||||
}
|
||||
|
||||
.switch--unselected:disabled .handle::before {
|
||||
.unselected.disabled .handle::before {
|
||||
background-color: var(--_disabled-handle-color);
|
||||
opacity: var(--_disabled-handle-opacity);
|
||||
}
|
||||
@ -135,7 +135,7 @@ $_easing-standard: map.get($_md-sys-motion, 'easing-standard');
|
||||
width: var(--_state-layer-size);
|
||||
}
|
||||
|
||||
.switch--selected md-ripple {
|
||||
.selected md-ripple {
|
||||
@include ripple.theme(
|
||||
(
|
||||
'hover-color': var(--_selected-hover-state-layer-color),
|
||||
@ -146,7 +146,7 @@ $_easing-standard: map.get($_md-sys-motion, 'easing-standard');
|
||||
);
|
||||
}
|
||||
|
||||
.switch--unselected md-ripple {
|
||||
.unselected md-ripple {
|
||||
@include ripple.theme(
|
||||
(
|
||||
'hover-color': var(--_hover-state-layer-color),
|
||||
|
@ -30,17 +30,17 @@ $_easing-standard: map.get($_md-sys-motion, 'easing-standard');
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.switch:disabled .icon {
|
||||
.disabled .icon {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.switch--selected .icon--on,
|
||||
.switch--unselected .icon--off {
|
||||
.selected .icon--on,
|
||||
.unselected .icon--off {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// rotate selected icon into view when there is no unselected icon
|
||||
.switch--unselected .handle:not(.with-icon) .icon--on {
|
||||
.unselected .handle:not(.with-icon) .icon--on {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
@ -50,19 +50,19 @@ $_easing-standard: map.get($_md-sys-motion, 'easing-standard');
|
||||
fill: var(--_icon-color);
|
||||
}
|
||||
|
||||
.switch--unselected:hover .icon--off {
|
||||
.unselected:hover .icon--off {
|
||||
fill: var(--_hover-icon-color);
|
||||
}
|
||||
|
||||
.switch--unselected:focus-within .icon--off {
|
||||
.unselected:focus-within .icon--off {
|
||||
fill: var(--_focus-icon-color);
|
||||
}
|
||||
|
||||
.switch--unselected:active .icon--off {
|
||||
.unselected:active .icon--off {
|
||||
fill: var(--_pressed-icon-color);
|
||||
}
|
||||
|
||||
.switch--unselected:disabled .icon--off {
|
||||
.unselected.disabled .icon--off {
|
||||
fill: var(--_disabled-icon-color);
|
||||
opacity: var(--_disabled-icon-opacity);
|
||||
}
|
||||
@ -73,19 +73,19 @@ $_easing-standard: map.get($_md-sys-motion, 'easing-standard');
|
||||
fill: var(--_selected-icon-color);
|
||||
}
|
||||
|
||||
.switch--selected:hover .icon--on {
|
||||
.selected:hover .icon--on {
|
||||
fill: var(--_selected-hover-icon-color);
|
||||
}
|
||||
|
||||
.switch--selected:focus-within .icon--on {
|
||||
.selected:focus-within .icon--on {
|
||||
fill: var(--_selected-focus-icon-color);
|
||||
}
|
||||
|
||||
.switch--selected:active .icon--on {
|
||||
.selected:active .icon--on {
|
||||
fill: var(--_selected-pressed-icon-color);
|
||||
}
|
||||
|
||||
.switch--selected:disabled .icon--on {
|
||||
.selected.disabled .icon--on {
|
||||
fill: var(--_disabled-selected-icon-color);
|
||||
opacity: var(--_disabled-selected-icon-opacity);
|
||||
}
|
||||
|
@ -106,14 +106,8 @@
|
||||
|
||||
.switch {
|
||||
align-items: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
flex-shrink: 0; // Stop from collapsing in flex containers
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
width: var(--_track-width);
|
||||
height: var(--_track-height);
|
||||
@ -125,34 +119,32 @@
|
||||
border-end-start-radius: var(--_track-shape-end-start);
|
||||
}
|
||||
|
||||
// Touch target
|
||||
.touch {
|
||||
position: absolute;
|
||||
// Input is also touch target
|
||||
input {
|
||||
appearance: none;
|
||||
height: 48px;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
:host([touch-target='none']) .touch {
|
||||
:host([touch-target='none']) input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Disabled
|
||||
.switch:disabled {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Disabled - Track
|
||||
.switch:disabled .track {
|
||||
.disabled .track {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.switch:disabled .track::before {
|
||||
.disabled .track::before {
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
.switch--selected:disabled .track {
|
||||
.selected.disabled .track {
|
||||
background-clip: border-box;
|
||||
}
|
||||
|
||||
|
@ -36,55 +36,55 @@
|
||||
transition-duration: 67ms;
|
||||
}
|
||||
|
||||
.switch:disabled .track::before,
|
||||
.switch:disabled .track::after {
|
||||
.disabled .track::before,
|
||||
.disabled .track::after {
|
||||
transition: none;
|
||||
opacity: var(--_disabled-track-opacity);
|
||||
}
|
||||
|
||||
.switch--selected .track::before {
|
||||
.selected .track::before {
|
||||
background-color: var(--_selected-track-color);
|
||||
}
|
||||
|
||||
.switch--selected:hover .track::before {
|
||||
.selected:hover .track::before {
|
||||
background-color: var(--_selected-hover-track-color);
|
||||
}
|
||||
|
||||
.switch--selected:focus-within .track::before {
|
||||
.selected:focus-within .track::before {
|
||||
background-color: var(--_selected-focus-track-color);
|
||||
}
|
||||
|
||||
.switch--selected:active .track::before {
|
||||
.selected:active .track::before {
|
||||
background-color: var(--_selected-pressed-track-color);
|
||||
}
|
||||
|
||||
.switch--selected:disabled .track::before {
|
||||
.selected.disabled .track::before {
|
||||
background-color: var(--_disabled-selected-track-color);
|
||||
}
|
||||
|
||||
.switch--unselected .track::before {
|
||||
.unselected .track::before {
|
||||
background-color: var(--_track-color);
|
||||
border-color: var(--_track-outline-color);
|
||||
border-style: solid;
|
||||
border-width: var(--_track-outline-width);
|
||||
}
|
||||
|
||||
.switch--unselected:hover .track::before {
|
||||
.unselected:hover .track::before {
|
||||
background-color: var(--_hover-track-color);
|
||||
border-color: var(--_hover-track-outline-color);
|
||||
}
|
||||
|
||||
.switch--unselected:focus-visible .track::before {
|
||||
.unselected:focus-visible .track::before {
|
||||
background-color: var(--_focus-track-color);
|
||||
border-color: var(--_focus-track-outline-color);
|
||||
}
|
||||
|
||||
.switch--unselected:active .track::before {
|
||||
.unselected:active .track::before {
|
||||
background-color: var(--_pressed-track-color);
|
||||
border-color: var(--_pressed-track-outline-color);
|
||||
}
|
||||
|
||||
.switch--unselected:disabled .track::before {
|
||||
.unselected.disabled .track::before {
|
||||
background-color: var(--_disabled-track-color);
|
||||
border-color: var(--_disabled-track-outline-color);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
'disabled-selected-icon-color': GrayText,
|
||||
'disabled-selected-icon-opacity': 1,
|
||||
'disabled-selected-track-color': GrayText,
|
||||
'disabled-track-outline-color': GrayText,
|
||||
'disabled-track-opacity': 1,
|
||||
'disabled-handle-color': GrayText,
|
||||
'disabled-handle-opacity': 1,
|
||||
|
@ -12,7 +12,7 @@ import {property, query} from 'lit/decorators.js';
|
||||
import {ClassInfo, classMap} from 'lit/directives/class-map.js';
|
||||
|
||||
import {requestUpdateOnAriaChange} from '../../internal/aria/delegate.js';
|
||||
import {dispatchActivationClick, isActivationClick} from '../../internal/controller/events.js';
|
||||
import {dispatchActivationClick, isActivationClick, redispatchEvent} from '../../internal/controller/events.js';
|
||||
|
||||
/**
|
||||
* @fires input {InputEvent} Fired whenever `selected` changes due to user
|
||||
@ -55,8 +55,13 @@ export class Switch extends LitElement {
|
||||
@property({type: Boolean, attribute: 'show-only-selected-icon'})
|
||||
showOnlySelectedIcon = false;
|
||||
|
||||
// Button
|
||||
@query('button') private readonly button!: HTMLButtonElement|null;
|
||||
/**
|
||||
* When true, require the switch to be selected when participating in
|
||||
* form submission.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#validation
|
||||
*/
|
||||
@property({type: Boolean}) required = false;
|
||||
|
||||
/**
|
||||
* The value associated with this switch on form submission. `null` is
|
||||
@ -88,6 +93,42 @@ export class Switch extends LitElement {
|
||||
return this.internals.labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ValidityState object that represents the validity states of the
|
||||
* switch.
|
||||
*
|
||||
* Note that switches will only set `valueMissing` if `required` and not
|
||||
* selected.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#validation
|
||||
*/
|
||||
get validity() {
|
||||
this.syncValidity();
|
||||
return this.internals.validity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the native validation error message.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#constraint_validation_process
|
||||
*/
|
||||
get validationMessage() {
|
||||
this.syncValidity();
|
||||
return this.internals.validationMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether an element will successfully validate based on forms
|
||||
* validation rules and constraints.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#constraint_validation_process
|
||||
*/
|
||||
get willValidate() {
|
||||
this.syncValidity();
|
||||
return this.internals.willValidate;
|
||||
}
|
||||
|
||||
@query('input') private readonly input!: HTMLInputElement|null;
|
||||
private readonly internals =
|
||||
(this as HTMLElement /* needed for closure */).attachInternals();
|
||||
|
||||
@ -98,15 +139,60 @@ export class Switch extends LitElement {
|
||||
if (!isActivationClick(event)) {
|
||||
return;
|
||||
}
|
||||
this.button?.focus();
|
||||
if (this.button != null) {
|
||||
// this triggers the click behavior, and the ripple
|
||||
dispatchActivationClick(this.button);
|
||||
}
|
||||
this.focus();
|
||||
dispatchActivationClick(this.input!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the switch's native validation and returns whether or not the
|
||||
* element is valid.
|
||||
*
|
||||
* If invalid, this method will dispatch the `invalid` event.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/checkValidity
|
||||
*
|
||||
* @return true if the switch is valid, or false if not.
|
||||
*/
|
||||
checkValidity() {
|
||||
this.syncValidity();
|
||||
return this.internals.checkValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the switch's native validation and returns whether or not the
|
||||
* element is valid.
|
||||
*
|
||||
* If invalid, this method will dispatch the `invalid` event.
|
||||
*
|
||||
* The `validationMessage` is reported to the user by the browser. Use
|
||||
* `setCustomValidity()` to customize the `validationMessage`.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/reportValidity
|
||||
*
|
||||
* @return true if the switch is valid, or false if not.
|
||||
*/
|
||||
reportValidity() {
|
||||
this.syncValidity();
|
||||
return this.internals.reportValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the switch's native validation error message. This is used to
|
||||
* customize `validationMessage`.
|
||||
*
|
||||
* When the error is not an empty string, the switch is considered invalid
|
||||
* and `validity.customError` will be true.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setCustomValidity
|
||||
*
|
||||
* @param error The error message to display.
|
||||
*/
|
||||
setCustomValidity(error: string) {
|
||||
this.internals.setValidity({customError: !!error}, error, this.getInput());
|
||||
}
|
||||
|
||||
protected override update(changed: PropertyValues<Switch>) {
|
||||
const state = String(this.selected);
|
||||
this.internals.setFormValue(this.selected ? this.value : null, state);
|
||||
@ -118,28 +204,38 @@ export class Switch extends LitElement {
|
||||
// content](https://html.spec.whatwg.org/multipage/dom.html#phrasing-content)
|
||||
// children, which includes custom elements, but not `div`s
|
||||
return html`
|
||||
<button
|
||||
id="switch"
|
||||
type="button"
|
||||
class="switch ${classMap(this.getRenderClasses())}"
|
||||
role="switch"
|
||||
aria-checked="${this.selected}"
|
||||
aria-label=${(this as ARIAMixin).ariaLabel || nothing}
|
||||
?disabled=${this.disabled}
|
||||
@click=${this.handleClick}
|
||||
>
|
||||
<md-focus-ring part="focus-ring"></md-focus-ring>
|
||||
<div class="switch ${classMap(this.getRenderClasses())}">
|
||||
<input
|
||||
id="switch"
|
||||
class="touch"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-label=${(this as ARIAMixin).ariaLabel || nothing}
|
||||
?checked=${this.selected}
|
||||
?disabled=${this.disabled}
|
||||
?required=${this.required}
|
||||
@change=${this.handleChange}
|
||||
>
|
||||
|
||||
<md-focus-ring part="focus-ring" for="switch"></md-focus-ring>
|
||||
<span class="track">
|
||||
${this.renderHandle()}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected override updated() {
|
||||
// Sync validity when properties change, since validation properties may
|
||||
// have changed.
|
||||
this.syncValidity();
|
||||
}
|
||||
|
||||
private getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
'switch--selected': this.selected,
|
||||
'switch--unselected': !this.selected,
|
||||
'selected': this.selected,
|
||||
'unselected': !this.selected,
|
||||
'disabled': this.disabled,
|
||||
};
|
||||
}
|
||||
|
||||
@ -197,17 +293,41 @@ export class Switch extends LitElement {
|
||||
return this.icons || this.showOnlySelectedIcon;
|
||||
}
|
||||
|
||||
private handleClick() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
private handleChange(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.selected = target.checked;
|
||||
redispatchEvent(this, event);
|
||||
}
|
||||
|
||||
private syncValidity() {
|
||||
// Sync the internal <input>'s validity and the host's ElementInternals
|
||||
// validity. We do this to re-use native `<input>` validation messages.
|
||||
const input = this.getInput();
|
||||
if (this.internals.validity.customError) {
|
||||
input.setCustomValidity(this.internals.validationMessage);
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
}
|
||||
|
||||
this.selected = !this.selected;
|
||||
this.dispatchEvent(
|
||||
new InputEvent('input', {bubbles: true, composed: true}));
|
||||
// Bubbles but does not compose to mimic native browser <input> & <select>
|
||||
// Additionally, native change event is not an InputEvent.
|
||||
this.dispatchEvent(new Event('change', {bubbles: true}));
|
||||
this.internals.setValidity(
|
||||
input.validity, input.validationMessage, this.getInput());
|
||||
}
|
||||
|
||||
private getInput() {
|
||||
if (!this.input) {
|
||||
// If the input is not yet defined, synchronously render.
|
||||
this.connectedCallback();
|
||||
this.performUpdate();
|
||||
}
|
||||
|
||||
if (this.isUpdatePending) {
|
||||
// If there are pending updates, synchronously perform them. This ensures
|
||||
// that constraint validation properties (like `required`) are synced
|
||||
// before interacting with input APIs that depend on them.
|
||||
this.scheduleUpdate();
|
||||
}
|
||||
|
||||
return this.input!;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
|
@ -76,30 +76,20 @@ describe('md-switch', () => {
|
||||
expect(toggle.selected).toBeFalse();
|
||||
});
|
||||
|
||||
it('sets `aria-checked` of button', () => {
|
||||
const toggleButton = toggle.shadowRoot!.querySelector('button')!;
|
||||
expect(toggleButton.getAttribute('aria-checked')).toEqual('false');
|
||||
|
||||
const selectedButton = selected.shadowRoot!.querySelector('button')!;
|
||||
expect(selectedButton.getAttribute('aria-checked')).toEqual('true');
|
||||
});
|
||||
|
||||
it('adds switch--selected class when true', () => {
|
||||
it('adds selected class when true', () => {
|
||||
const toggleRoot = toggle.shadowRoot!.querySelector('.switch')!;
|
||||
expect(Array.from(toggleRoot.classList))
|
||||
.not.toContain('switch--selected');
|
||||
expect(Array.from(toggleRoot.classList)).not.toContain('selected');
|
||||
|
||||
const selectedRoot = selected.shadowRoot!.querySelector('.switch')!;
|
||||
expect(Array.from(selectedRoot.classList)).toContain('switch--selected');
|
||||
expect(Array.from(selectedRoot.classList)).toContain('selected');
|
||||
});
|
||||
|
||||
it('adds switch--unselected class when false', () => {
|
||||
it('adds unselected class when false', () => {
|
||||
const toggleRoot = toggle.shadowRoot!.querySelector('.switch')!;
|
||||
expect(Array.from(toggleRoot.classList)).toContain('switch--unselected');
|
||||
expect(Array.from(toggleRoot.classList)).toContain('unselected');
|
||||
|
||||
const selectedRoot = selected.shadowRoot!.querySelector('.switch')!;
|
||||
expect(Array.from(selectedRoot.classList))
|
||||
.not.toContain('switch--unselected');
|
||||
expect(Array.from(selectedRoot.classList)).not.toContain('unselected');
|
||||
});
|
||||
});
|
||||
|
||||
@ -114,32 +104,32 @@ describe('md-switch', () => {
|
||||
expect(toggle.disabled).toBeFalse();
|
||||
});
|
||||
|
||||
it('sets disabled of button', () => {
|
||||
const toggleButton = toggle.shadowRoot!.querySelector('button')!;
|
||||
expect(toggleButton.disabled).toBeFalse();
|
||||
it('sets disabled of input', () => {
|
||||
const toggleInput = toggle.shadowRoot!.querySelector('input')!;
|
||||
expect(toggleInput.disabled).toBeFalse();
|
||||
|
||||
const selectedButton = disabled.shadowRoot!.querySelector('button')!;
|
||||
expect(selectedButton.disabled).toBeTrue();
|
||||
const selectedInput = disabled.shadowRoot!.querySelector('input')!;
|
||||
expect(selectedInput.disabled).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('aria', () => {
|
||||
it('delegates aria-label to the proper element', async () => {
|
||||
const button = toggle.shadowRoot!.querySelector('button')!;
|
||||
const input = toggle.shadowRoot!.querySelector('input')!;
|
||||
toggle.setAttribute('aria-label', 'foo');
|
||||
await toggle.updateComplete;
|
||||
expect(toggle.ariaLabel).toEqual('foo');
|
||||
expect(toggle.getAttribute('aria-label')).toEqual('foo');
|
||||
expect(button.getAttribute('aria-label')).toEqual('foo');
|
||||
expect(input.getAttribute('aria-label')).toEqual('foo');
|
||||
});
|
||||
|
||||
it('delegates .ariaLabel to the proper element', async () => {
|
||||
const button = toggle.shadowRoot!.querySelector('button')!;
|
||||
const input = toggle.shadowRoot!.querySelector('input')!;
|
||||
toggle.ariaLabel = 'foo';
|
||||
await toggle.updateComplete;
|
||||
expect(toggle.ariaLabel).toEqual('foo');
|
||||
expect(toggle.getAttribute('aria-label')).toEqual('foo');
|
||||
expect(button.getAttribute('aria-label')).toEqual('foo');
|
||||
expect(input.getAttribute('aria-label')).toEqual('foo');
|
||||
});
|
||||
});
|
||||
|
||||
@ -225,4 +215,23 @@ describe('md-switch', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
it('should set valueMissing when required and not selected', async () => {
|
||||
toggle.required = true;
|
||||
|
||||
expect(toggle.validity.valueMissing)
|
||||
.withContext('toggle.validity.valueMissing')
|
||||
.toBeTrue();
|
||||
});
|
||||
|
||||
it('should not set valueMissing when required and selected', async () => {
|
||||
toggle.required = true;
|
||||
toggle.selected = true;
|
||||
|
||||
expect(toggle.validity.valueMissing)
|
||||
.withContext('toggle.validity.valueMissing')
|
||||
.toBeFalse();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user