Skip to content

Commit

Permalink
Merge branch 'main' into VIV-2124-docs-select-page
Browse files Browse the repository at this point in the history
  • Loading branch information
TaylorJ76 authored Oct 30, 2024
2 parents 92ba4a9 + c5dd033 commit 13f5588
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 14 deletions.
12 changes: 12 additions & 0 deletions libs/components/src/lib/checkbox/checkbox.form-associated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {
CheckableFormAssociated,
FoundationElement,
} from '@microsoft/fast-foundation';

class _Checkbox extends FoundationElement {}
// eslint-disable-next-line @typescript-eslint/naming-convention
interface _Checkbox extends CheckableFormAssociated {}

export class FormAssociatedCheckbox extends CheckableFormAssociated(_Checkbox) {
proxy = document.createElement('input');
}
98 changes: 92 additions & 6 deletions libs/components/src/lib/checkbox/checkbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,56 @@ describe('vwc-checkbox', () => {
getBaseElement(element).classList.contains('checked')
).toBeTruthy();
});

it('should toggle checked when clicked', () => {
getBaseElement(element).click();

expect(element.checked).toBe(true);

getBaseElement(element).click();

expect(element.checked).toBe(false);
});

it('should not toggle checked when clicked while disabled', () => {
element.disabled = true;

getBaseElement(element).click();

expect(element.checked).toBe(false);
});

it('should not toggle checked when clicked while readOnly', () => {
element.readOnly = true;

getBaseElement(element).click();

expect(element.checked).toBe(false);
});

it('should toggle checked when pressing Space', () => {
getBaseElement(element).dispatchEvent(
new KeyboardEvent('keypress', { key: ' ' })
);

expect(element.checked).toBe(true);

getBaseElement(element).dispatchEvent(
new KeyboardEvent('keypress', { key: ' ' })
);

expect(element.checked).toBe(false);
});

it('should not toggle checked when pressing Space while readOnly', () => {
element.readOnly = true;

getBaseElement(element).dispatchEvent(
new KeyboardEvent('keypress', { key: ' ' })
);

expect(element.checked).toBe(false);
});
});

describe('disabled', function () {
Expand All @@ -82,6 +132,17 @@ describe('vwc-checkbox', () => {
await elementUpdated(element);
expect(element.shadowRoot?.querySelector('.disabled')).toBeTruthy();
});

it('should set aria-disabled attribute when disabled is true', async () => {
expect(getBaseElement(element).getAttribute('aria-disabled')).toBe(
'false'
);
element.disabled = true;
await elementUpdated(element);
expect(getBaseElement(element).getAttribute('aria-disabled')).toBe(
'true'
);
});
});

describe('readonly', function () {
Expand All @@ -93,6 +154,19 @@ describe('vwc-checkbox', () => {
});
});

describe('required', function () {
it('should set aria-required attribute when required is true', async () => {
expect(getBaseElement(element).getAttribute('aria-required')).toBe(
'false'
);
element.required = true;
await elementUpdated(element);
expect(getBaseElement(element).getAttribute('aria-required')).toBe(
'true'
);
});
});

describe('indeterminate', () => {
it('should set checked class when indeterminate is true', async () => {
element.indeterminate = true;
Expand Down Expand Up @@ -236,12 +310,8 @@ describe('vwc-checkbox', () => {

const submitPromise = listenToFormSubmission(formElement);
formElement.requestSubmit();
(await submitPromise).forEach(
(formDataValue: any, formDataKey: string) => {
expect(formDataKey).toEqual(fieldName);
expect(formDataValue).toEqual(checked);
}
);
const submitResult = await submitPromise;
expect(submitResult.get(fieldName)).toBe(checked);
});
});

Expand Down Expand Up @@ -327,6 +397,22 @@ describe('vwc-checkbox', () => {
expect(baseElement?.getAttribute('role')).toBe('checkbox');
});

it('should set aria-required attribute when required is true', async () => {
element.required = true;
await elementUpdated(element);
expect(getBaseElement(element).getAttribute('aria-required')).toBe(
'true'
);
});

it('should set aria-readonly attribute when readOnly is true', async () => {
element.readOnly = true;
await elementUpdated(element);
expect(getBaseElement(element).getAttribute('aria-readonly')).toBe(
'true'
);
});

describe('aria-label', () => {
beforeEach(async () => {
element.ariaLabel = 'Label';
Expand Down
55 changes: 47 additions & 8 deletions libs/components/src/lib/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Checkbox as FoundationCheckbox } from '@microsoft/fast-foundation';
import { attr, observable } from '@microsoft/fast-element';
import type { Connotation } from '../enums.js';
import {
Expand All @@ -10,6 +9,7 @@ import {
FormElementSuccessText,
} from '../../shared/patterns';
import { applyMixinsWithObservables } from '../../shared/utils/applyMixinsWithObservables';
import { FormAssociatedCheckbox } from './checkbox.form-associated';

export const keySpace: ' ' = ' ' as const;

Expand All @@ -35,7 +35,7 @@ export type AriaCheckedStates = 'false' | 'true' | 'mixed' | 'undefined';
*/
@errorText
@formElements
export class Checkbox extends FoundationCheckbox {
export class Checkbox extends FormAssociatedCheckbox {
@attr({ attribute: 'aria-label' }) override ariaLabel: string | null = null;
@attr({ attribute: 'tabindex' }) tabindex: string | null = null;

Expand All @@ -58,6 +58,49 @@ export class Checkbox extends FoundationCheckbox {
@attr({ attribute: 'aria-checked' })
override ariaChecked: AriaCheckedStates | null = null;

/**
* When true, the control will be immutable by user interaction. See {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly | readonly HTML attribute} for more information.
* @public
* @remarks
* HTML Attribute: readonly
*/
@attr({ attribute: 'readonly', mode: 'boolean' })
readOnly!: boolean; // Map to proxy element
/**
* @internal
*/
readOnlyChanged() {
if (this.proxy instanceof HTMLInputElement) {
this.proxy.readOnly = this.readOnly;
}
}

/**
* The element's value to be included in form submission when checked.
* Default to "on" to reach parity with input[type="checkbox"]
*
* @internal
*/
override initialValue = 'on';

/**
* @internal
*/
@observable
defaultSlottedNodes: Node[] = [];

/**
* The indeterminate state of the control
*/
@observable
indeterminate = false;

constructor() {
super();

this.proxy.setAttribute('type', 'checkbox');
}

indeterminateChanged(_: boolean, next: boolean) {
this.checked = !next;
}
Expand All @@ -83,11 +126,9 @@ export class Checkbox extends FoundationCheckbox {
}

/**
* !remove method as will be implemented by fast-foundation in version after 2.46.9
*
* @internal
*/
override keypressHandler = (event: KeyboardEvent): boolean => {
keypressHandler = (event: KeyboardEvent): boolean => {
if (event.target instanceof HTMLAnchorElement) {
return true;
}
Expand All @@ -106,11 +147,9 @@ export class Checkbox extends FoundationCheckbox {
};

/**
* !remove method as will be implemented by fast-foundation in version after 2.46.9
*
* @internal
*/
override clickHandler = (event: Event): boolean => {
clickHandler = (event: Event): boolean => {
if (event.target instanceof HTMLAnchorElement) {
return true;
}
Expand Down

0 comments on commit 13f5588

Please sign in to comment.