Skip to content

Commit

Permalink
Merge pull request #832 from Trendyol/checkbox-error-state
Browse files Browse the repository at this point in the history
fix(checkbox): fix error state of checkbox/ checkbox group
  • Loading branch information
ozkersemih authored Apr 26, 2024
2 parents c21191d + a4ec6f3 commit 0968a8f
Show file tree
Hide file tree
Showing 8 changed files with 589 additions and 43 deletions.
27 changes: 27 additions & 0 deletions src/components/checkbox-group/bl-checkbox-group.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,30 @@ legend {
gap: var(--bl-size-m);
margin-block: var(--bl-size-xs);
}

.dirty.invalid .options {
margin-bottom: var(--bl-size-3xs);
}

.hint {
display: none;
font: var(--bl-font-body-text-3);
}

.hint p {
padding: 0;
margin: 0;
}

.invalid-text {
display: none;
color: var(--bl-color-danger);
}

.dirty.invalid .hint {
display: block;
}

.dirty.invalid .invalid-text {
display: block;
}
37 changes: 37 additions & 0 deletions src/components/checkbox-group/bl-checkbox-group.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const CheckboxGroupTemplate = (args) => html`
name='${ifDefined(args.name)}'
value='${ifDefined(args.value)}'
?required=${args.required}
invalid-text='${ifDefined(args.customInvalidText)}'
>${args.options.map((option) =>
html`\n <bl-checkbox value=${option.value}>${option.label}</bl-checkbox>`
)}
Expand All @@ -48,6 +49,12 @@ export const StyledTemplate = (args) => html`
${CheckboxGroupTemplate(args)}
`

export const FormTemplate = (args) => html`<form class="stacked-form" novalidate>
${CheckboxGroupTemplate(args)}
<bl-button type="submit">Submit</bl-button>
</form>
`

export const focusSecondOption = async ({ }) => {
await userEvent.tab();
await userEvent.keyboard('{ArrowRight}');
Expand Down Expand Up @@ -80,6 +87,36 @@ By default checkbox options are listed in vertical stack. You can change this by
</Story>
</Canvas>

## Checkbox Group Validation

Checkbox Group component has 'required' validation rule. If there is no selected checkbox in checkbox group with 'required' attribute, component will not be validated.

Also custom invalid text can be passed by 'invalid-text' attribute.

<Canvas isColumn>
<Story name="Validation"
args={{label: 'Checkbox Group', name: 'favoriteAnimals', options: [{ value: 'cat', label: 'Cat'}, {value: 'dog', label: 'Dog'}],class: 'favorite-animals', required: true}}
>
{StyledTemplate.bind({})}
</Story>

<Story name="Validation with custom text"
args={{label: 'Checkbox Group with Custom Invalid Text', name: 'favoriteAnimals', options: [{ value: 'cat', label: 'Cat'}, {value: 'dog', label: 'Dog'}],class: 'favorite-animals', required: true, customInvalidText: "Please select at least 1 option"}}
>
{StyledTemplate.bind({})}
</Story>
</Canvas>

## Using within a form

<Canvas>
<Story name="Form Usage"
args={{label: 'Favorite Animals', name: 'favoriteAnimals', options: [{ value: 'cat', label: 'Cat'}, {value: 'dog', label: 'Dog'}],class: 'favorite-animals', required: true}}
>
{FormTemplate.bind({})}
</Story>
</Canvas>

## Reference

<ArgsTable of="bl-checkbox-group" />
156 changes: 146 additions & 10 deletions src/components/checkbox-group/bl-checkbox-group.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { elementUpdated, expect, fixture, html } from "@open-wc/testing";
import { elementUpdated, expect, fixture, html, oneEvent } from "@open-wc/testing";
import { sendKeys } from "@web/test-runner-commands";
import BlCheckboxGroup from "./bl-checkbox-group";
import "./checkbox/bl-checkbox";
Expand All @@ -23,13 +23,15 @@ describe("bl-checkbox-group", () => {

//then
expect(el).shadowDom.equal(
`
<fieldset role="group" aria-labelledby="label" aria-required="false">
<legend id="label">Choose sports you like</legend>
<div class="options">
<slot></slot>
</div>
</fieldset>
`<div>
<fieldset role="group" aria-labelledby="label" aria-required="false" tabindex="0">
<legend id="label">Choose sports you like</legend>
<div class="options">
<slot></slot>
</div>
<div class="hint"></div>
</fieldset>
</div>
`
);
});
Expand Down Expand Up @@ -243,8 +245,8 @@ describe("bl-checkbox-group", () => {
});

//then
expect(checkboxGroup?.value.length).to.equal(1);
expect(checkboxGroup?.value[0]).to.equal("basketball");
expect(checkboxGroup?.value?.length).to.equal(1);
expect(checkboxGroup?.value?.[0]).to.equal("basketball");
});

it("should focus the next option with Tab key & previous option with Shift+Tab key", async () => {
Expand Down Expand Up @@ -348,5 +350,139 @@ describe("bl-checkbox-group", () => {
//then
expect(document.activeElement).to.equal(checkboxGroup?.options[0]);
});

describe("validation", () => {
it("should be valid by default", async () => {
const el = await fixture<BlCheckboxGroup>(
html`<bl-checkbox-group>
<bl-checkbox value="basketball">Basketball</bl-checkbox>
<bl-checkbox value="football">Football</bl-checkbox>
<bl-checkbox value="tennis">Tennis</bl-checkbox>
</bl-checkbox-group>`
);

expect(el.validity.valid).to.be.true;
});

it("should be invalid with required attribute", async () => {
const el = await fixture<BlCheckboxGroup>(
html`<bl-checkbox-group required>
<bl-checkbox value="basketball">Basketball</bl-checkbox>
<bl-checkbox value="football">Football</bl-checkbox>
<bl-checkbox value="tennis">Tennis</bl-checkbox>
</bl-checkbox-group>`
);

expect(el.validity.valid).to.be.false;

});

it("should set invalid text", async () => {
const errorMessage = "This field is mandatory";
const el = await fixture<BlCheckboxGroup>(
html`<bl-checkbox-group required invalid-text="${errorMessage}">
<bl-checkbox value="basketball">Basketball</bl-checkbox>
<bl-checkbox value="football">Football</bl-checkbox>
<bl-checkbox value="tennis">Tennis</bl-checkbox>
</bl-checkbox-group>`
);


el.reportValidity();

await elementUpdated(el);

const errorMessageElement = <HTMLParagraphElement>(
el.shadowRoot?.querySelector(".invalid-text")
);

expect(el.validity.valid).to.be.false;

expect(errorMessageElement).to.exist;
expect(errorMessageElement?.innerText).to.equal(errorMessage);
});

it("should show error when reportValidity method called", async () => {
const el = await fixture<BlCheckboxGroup>(
html`<bl-checkbox-group required>
<bl-checkbox value="basketball">Basketball</bl-checkbox>
<bl-checkbox value="football">Football</bl-checkbox>
<bl-checkbox value="tennis">Tennis</bl-checkbox>
</bl-checkbox-group>`
);

el.reportValidity();

await elementUpdated(el);

expect(el.validity.valid).to.be.false;
const errorMessageElement = <HTMLParagraphElement>(
el.shadowRoot?.querySelector(".invalid-text")
);

expect(errorMessageElement).to.visible;
});

it("should show error when make some options unchecked", async () => {
const checkboxGroup = await fixture<BlCheckboxGroup>(
html`<bl-checkbox-group label="Choose sports you like" value="[&quot;basketball&quot;]" required>
<bl-checkbox value="basketball">Basketball</bl-checkbox>
<bl-checkbox value="football">Football</bl-checkbox>
<bl-checkbox value="tennis">Tennis</bl-checkbox>
</bl-checkbox-group>`
);

checkboxGroup.reportValidity();

expect(checkboxGroup.validity.valid).to.be.true;


await elementUpdated(checkboxGroup);

const firstCheckbox = checkboxGroup.options[0].shadowRoot?.querySelector("input");

setTimeout(() => firstCheckbox?.click());
const invalidEvent = await oneEvent(checkboxGroup, "bl-checkbox-group-invalid");

await new Promise(resolve => setTimeout(resolve, 1000));
await elementUpdated(checkboxGroup);


expect(invalidEvent).to.exist;
expect(checkboxGroup.validity.valid).to.be.false;
const errorMessageElement = <HTMLParagraphElement>(
checkboxGroup.shadowRoot?.querySelector(".invalid-text")
);

expect(errorMessageElement).to.visible;
});
});
describe("form integration", () => {
it("should show errors when parent form is submitted", async () => {
const form = await fixture<HTMLFormElement>(html`<form novalidate>
<bl-checkbox-group label="Choose sports you like" required>
<bl-checkbox value="basketball">Basketball</bl-checkbox>
<bl-checkbox value="football">Football</bl-checkbox>
<bl-checkbox value="tennis">Tennis</bl-checkbox>
</bl-checkbox-group>
</form>`);

const blCheckboxGroup = form.querySelector<BlCheckboxGroup>("bl-checkbox-group");

form.addEventListener("submit", e => e.preventDefault());

form.dispatchEvent(new SubmitEvent("submit", { cancelable: true }));

await elementUpdated(form);

const errorMessageElement = <HTMLParagraphElement>(
blCheckboxGroup?.shadowRoot?.querySelector(".invalid-text")
);

expect(blCheckboxGroup?.validity.valid).to.be.false;

expect(errorMessageElement).to.exist;
});
});
});
});
Loading

0 comments on commit 0968a8f

Please sign in to comment.