validate even with novalidate; fixes #1164

This commit is contained in:
Cory LaViska
2023-02-06 17:18:01 -05:00
parent af70d88153
commit a539058253
9 changed files with 107 additions and 17 deletions

View File

@@ -15,6 +15,7 @@ New versions of Shoelace are released as-needed and generally occur when a criti
- Fixed a bug in the template for `<sl-select>` that caused the `form-control-help-text` part to not be in the same location as other form controls [#1178](https://github.com/shoelace-style/shoelace/issues/1178)
- Fixed a bug in `<sl-checkbox>` and `<sl-switch>` that caused the browser to scroll incorrectly when focusing on a control in a container with overflow [#1169](https://github.com/shoelace-style/shoelace/issues/1169)
- Fixed a bug in `<sl-menu-item>` that caused the `click` event to be emitted when the item was disabled [#1113](https://github.com/shoelace-style/shoelace/issues/1113)
- Fixed a bug in form controls that erroneously prevented validation states from being set when `novalidate` was used on the containing form [#1164](https://github.com/shoelace-style/shoelace/issues/1164)
- Improved the behavior of `<sl-dropdown>` in Safari so keyboard interaction works the same as in other browsers [#1177](https://github.com/shoelace-style/shoelace/issues/1177)
- Improved the [icons](/components/icon) page so it's not as sluggish in Safari [#1122](https://github.com/shoelace-style/shoelace/issues/1122)

View File

@@ -199,6 +199,18 @@ describe('<sl-checkbox>', () => {
expect(formData.get('a')).to.equal('1');
});
it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => {
const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-checkbox required></sl-checkbox></form> `);
const checkbox = el.querySelector<SlCheckbox>('sl-checkbox')!;
expect(checkbox.hasAttribute('data-required')).to.be.true;
expect(checkbox.hasAttribute('data-optional')).to.be.false;
expect(checkbox.hasAttribute('data-invalid')).to.be.true;
expect(checkbox.hasAttribute('data-valid')).to.be.false;
expect(checkbox.hasAttribute('data-user-invalid')).to.be.false;
expect(checkbox.hasAttribute('data-user-valid')).to.be.false;
});
});
describe('when resetting a form', () => {

View File

@@ -155,6 +155,18 @@ describe('<sl-input>', () => {
expect(el.hasAttribute('data-user-invalid')).to.be.true;
expect(el.hasAttribute('data-user-valid')).to.be.false;
});
it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => {
const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-input required></sl-input></form> `);
const input = el.querySelector<SlInput>('sl-input')!;
expect(input.hasAttribute('data-required')).to.be.true;
expect(input.hasAttribute('data-optional')).to.be.false;
expect(input.hasAttribute('data-invalid')).to.be.true;
expect(input.hasAttribute('data-valid')).to.be.false;
expect(input.hasAttribute('data-user-invalid')).to.be.false;
expect(input.hasAttribute('data-user-valid')).to.be.false;
});
});
describe('when submitting a form', () => {

View File

@@ -130,6 +130,25 @@ describe('<sl-radio-group>', () => {
expect(radioGroup.hasAttribute('data-user-invalid')).to.be.true;
expect(radioGroup.hasAttribute('data-user-valid')).to.be.false;
});
it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => {
const el = await fixture<HTMLFormElement>(html`
<form novalidate>
<sl-radio-group required>
<sl-radio value="1"></sl-radio>
<sl-radio value="2"></sl-radio>
</sl-radio-group>
</form>
`);
const radioGroup = el.querySelector<SlRadioGroup>('sl-radio-group')!;
expect(radioGroup.hasAttribute('data-required')).to.be.true;
expect(radioGroup.hasAttribute('data-optional')).to.be.false;
expect(radioGroup.hasAttribute('data-invalid')).to.be.true;
expect(radioGroup.hasAttribute('data-valid')).to.be.false;
expect(radioGroup.hasAttribute('data-user-invalid')).to.be.false;
expect(radioGroup.hasAttribute('data-user-valid')).to.be.false;
});
});
it('should show a constraint validation error when setCustomValidity() is called', async () => {

View File

@@ -169,6 +169,19 @@ describe('<sl-range>', () => {
expect(range.hasAttribute('data-user-valid')).to.be.false;
});
it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => {
const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-range></sl-range></form> `);
const range = el.querySelector<SlRange>('sl-range')!;
range.setCustomValidity('Invalid value');
await range.updateComplete;
expect(range.hasAttribute('data-invalid')).to.be.true;
expect(range.hasAttribute('data-valid')).to.be.false;
expect(range.hasAttribute('data-user-invalid')).to.be.false;
expect(range.hasAttribute('data-user-valid')).to.be.false;
});
it('should be present in form data when using the form attribute and located outside of a <form>', async () => {
const el = await fixture<HTMLFormElement>(html`
<div>

View File

@@ -294,6 +294,26 @@ describe('<sl-select>', () => {
expect(el.hasAttribute('data-user-invalid')).to.be.true;
expect(el.hasAttribute('data-user-valid')).to.be.false;
});
it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => {
const el = await fixture<HTMLFormElement>(html`
<form novalidate>
<sl-select required>
<sl-option value="option-1">Option 1</sl-option>
<sl-option value="option-2">Option 2</sl-option>
<sl-option value="option-3">Option 3</sl-option>
</sl-select>
</form>
`);
const select = el.querySelector<SlSelect>('sl-select')!;
expect(select.hasAttribute('data-required')).to.be.true;
expect(select.hasAttribute('data-optional')).to.be.false;
expect(select.hasAttribute('data-invalid')).to.be.true;
expect(select.hasAttribute('data-valid')).to.be.false;
expect(select.hasAttribute('data-user-invalid')).to.be.false;
expect(select.hasAttribute('data-user-valid')).to.be.false;
});
});
describe('when submitting a form', () => {

View File

@@ -217,6 +217,18 @@ describe('<sl-switch>', () => {
expect(formData.get('a')).to.equal('1');
});
it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => {
const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-switch required></sl-switch></form> `);
const slSwitch = el.querySelector<SlSwitch>('sl-switch')!;
expect(slSwitch.hasAttribute('data-required')).to.be.true;
expect(slSwitch.hasAttribute('data-optional')).to.be.false;
expect(slSwitch.hasAttribute('data-invalid')).to.be.true;
expect(slSwitch.hasAttribute('data-valid')).to.be.false;
expect(slSwitch.hasAttribute('data-user-invalid')).to.be.false;
expect(slSwitch.hasAttribute('data-user-valid')).to.be.false;
});
});
describe('when resetting a form', () => {

View File

@@ -171,6 +171,18 @@ describe('<sl-textarea>', () => {
expect(el.hasAttribute('data-user-invalid')).to.be.true;
expect(el.hasAttribute('data-user-valid')).to.be.false;
});
it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => {
const el = await fixture<HTMLFormElement>(html` <form novalidate><sl-textarea required></sl-textarea></form> `);
const textarea = el.querySelector<SlTextarea>('sl-textarea')!;
expect(textarea.hasAttribute('data-required')).to.be.true;
expect(textarea.hasAttribute('data-optional')).to.be.false;
expect(textarea.hasAttribute('data-invalid')).to.be.true;
expect(textarea.hasAttribute('data-valid')).to.be.false;
expect(textarea.hasAttribute('data-user-invalid')).to.be.false;
expect(textarea.hasAttribute('data-user-valid')).to.be.false;
});
});
describe('when submitting a form', () => {

View File

@@ -294,23 +294,12 @@ export class FormControlController implements ReactiveController {
//
// See this RFC for more details: https://github.com/shoelace-style/shoelace/issues/1011
//
if (this.form?.noValidate) {
// Form validation is disabled, remove the attributes
host.removeAttribute('data-required');
host.removeAttribute('data-optional');
host.removeAttribute('data-invalid');
host.removeAttribute('data-valid');
host.removeAttribute('data-user-invalid');
host.removeAttribute('data-user-valid');
} else {
// Form validation is enabled, set the attributes
host.toggleAttribute('data-required', required);
host.toggleAttribute('data-optional', !required);
host.toggleAttribute('data-invalid', !isValid);
host.toggleAttribute('data-valid', isValid);
host.toggleAttribute('data-user-invalid', !isValid && hasInteracted);
host.toggleAttribute('data-user-valid', isValid && hasInteracted);
}
host.toggleAttribute('data-required', required);
host.toggleAttribute('data-optional', !required);
host.toggleAttribute('data-invalid', !isValid);
host.toggleAttribute('data-valid', isValid);
host.toggleAttribute('data-user-invalid', !isValid && hasInteracted);
host.toggleAttribute('data-user-valid', isValid && hasInteracted);
}
/**