Skip to content

Commit

Permalink
test(atomic): add lit checkbox tests (#4899)
Browse files Browse the repository at this point in the history
* Add checkbox render function for lit components
* Add checkbox unit tests 
* Renamed stencil checkbox functional component and interface

https://coveord.atlassian.net/browse/KIT-3835

---------

Co-authored-by: Frederic Beaudoin <[email protected]>
  • Loading branch information
y-lakhdar and fbeaudoincoveo authored Feb 4, 2025
1 parent 5bb6b51 commit 694475f
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 9 deletions.
167 changes: 167 additions & 0 deletions packages/atomic/src/components/common/checkbox.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {fireEvent, within} from '@storybook/test';
import {html, render} from 'lit';
import {vi} from 'vitest';
import {checkbox, CheckboxProps} from './checkbox';

describe('checkbox', () => {
let container: HTMLElement;

beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});

afterEach(() => {
document.body.removeChild(container);
});

const renderCheckbox = (props: Partial<CheckboxProps>): HTMLButtonElement => {
render(
html`${checkbox({
...props,
checked: props.checked ?? false,
onToggle: props.onToggle ?? vi.fn(),
})}`,
container
);
return within(container).getByRole('checkbox') as HTMLButtonElement;
};

it('should render a checkbox in the document', () => {
const props = {};
const button = renderCheckbox(props);
expect(button).toBeInTheDocument();
});

it('should render a checkbox with the correct text attributes', () => {
const props = {
id: 'some_id',
};

const button = renderCheckbox(props);

expect(button?.id).toBe('some_id');
});

it('should render a checkbox with the correct text attributes', () => {
const props = {
text: 'Test Checkbox',
};

const button = renderCheckbox(props);

expect(button.getAttribute('aria-label')).toBe('Test Checkbox');
expect(button.value).toBe('Test Checkbox');
});

it('should render a checkbox with the correct text attributes', () => {
const props = {
text: 'Test Checkbox',
ariaLabel: 'Aria Label Value',
};

const button = renderCheckbox(props);

expect(button.getAttribute('aria-label')).toBe('Aria Label Value');
expect(button.value).toBe('Test Checkbox');
});

it('should not be checked by default', async () => {
const button = renderCheckbox({});

expect(button.getAttribute('aria-checked')).toBe('false');
expect(button.classList.contains('selected')).toBe(false);
});

it('should have selected attributes and classes if checked', async () => {
const button = renderCheckbox({checked: true});

expect(button.getAttribute('aria-checked')).toBe('true');
expect(button.classList.contains('selected')).toBe(true);
});

it('should not have selected attributes and classes if not checked', async () => {
const button = renderCheckbox({checked: false});

expect(button.getAttribute('aria-checked')).toBe('false');
expect(button.classList.contains('selected')).toBe(false);
});

it('should call onToggle when the checkbox is clicked', async () => {
const onToggle = vi.fn();
const props = {
onToggle,
};

const button = renderCheckbox(props);

await fireEvent.click(button);

expect(onToggle).toHaveBeenCalledWith(true);
});

it('should render a checkbox with the correct class', () => {
const props = {
class: 'test-class',
};

const button = renderCheckbox(props);

expect(button).toHaveClass('test-class');
expect(button).toHaveClass(
'w-4',
// TODO: KIT-3907
// @ts-expect-error the typing is incorrect. matchers should be a string[]
'h-4',
'grid',
'place-items-center',
'rounded',
'no-outline',
'hover:border-primary-light',
'focus-visible:border-primary-light'
);
});

it('should render a checkbox with the correct part attribute', () => {
const props = {
part: 'test-part',
};

const button = renderCheckbox(props);

expect(button.getAttribute('part')).toBe('test-part');
});

it('should render a checkbox with the correct ref', () => {
const ref = vi.fn();
const props = {
ref,
};

renderCheckbox(props);

expect(ref).toHaveBeenCalled();
});

it('should render a checkbox with the correct aria-current attribute', () => {
const props: Partial<CheckboxProps> = {
ariaCurrent: 'page',
};

const button = renderCheckbox(props);

expect(button.getAttribute('aria-current')).toBe('page');
});

it('should call onMouseDown when the mousedown event is fired on the checkbox', async () => {
const onMouseDown = vi.fn();
const props = {
onMouseDown,
};

const button = renderCheckbox(props);
await fireEvent.mouseDown(button);

expect(onMouseDown).toHaveBeenCalled();
});
});
72 changes: 72 additions & 0 deletions packages/atomic/src/components/common/checkbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {html, TemplateResult} from 'lit';
import {classMap} from 'lit/directives/class-map.js';
import {ifDefined} from 'lit/directives/if-defined.js';
import {ref, RefOrCallback} from 'lit/directives/ref.js';
import Tick from '../../images/checkbox.svg';

export interface CheckboxProps {
checked?: boolean;
onToggle(checked: boolean): void;
key?: string | number;
id?: string;
class?: string;
text?: string;
part?: string;
iconPart?: string;
ariaLabel?: string;
ariaCurrent?:
| 'page'
| 'step'
| 'location'
| 'date'
| 'time'
| 'true'
| 'false';
ref?: RefOrCallback;
onMouseDown?(evt: MouseEvent): void;
}

export const checkbox = (props: CheckboxProps): TemplateResult => {
const partName = props.part ?? 'checkbox';
const baseClassNames =
'w-4 h-4 grid place-items-center rounded no-outline hover:border-primary-light focus-visible:border-primary-light';
const selectedClassNames =
'selected bg-primary hover:bg-primary-light focus-visible:bg-primary-light';
const unSelectedClassNames = 'border border-neutral-dark';

const classNames = {
[baseClassNames]: true,
[selectedClassNames]: Boolean(props.checked),
[unSelectedClassNames]: !props.checked,
[props.class ?? '']: Boolean(props.class),
};

const parts = [partName];
if (props.checked) {
parts.push(`${partName}-checked`);
}

return html`
<button
.key=${props.key}
id=${ifDefined(props.id)}
class=${classMap(classNames)}
part=${parts.join(' ')}
aria-checked=${ifDefined(props.checked)}
aria-current=${ifDefined(props.ariaCurrent)}
aria-label=${ifDefined(props.ariaLabel ?? props.text)}
value=${ifDefined(props.text)}
role="checkbox"
@click=${() => props.onToggle?.(!props.checked)}
@mousedown=${(e: MouseEvent) => props.onMouseDown?.(e)}
${ref(props.ref)}
>
<atomic-icon
style="stroke: white"
class="${props.checked ? 'block' : 'hidden'} w-3/5"
.icon=${Tick}
part=${ifDefined(props.iconPart)}
></atomic-icon>
</button>
`;
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {FunctionalComponent, h} from '@stencil/core';
import {createRipple} from '../../../../utils/ripple';
import {randomID} from '../../../../utils/utils';
import {Checkbox} from '../../checkbox';
import {StencilCheckbox} from '../../stencil-checkbox';
import {TriStateCheckbox} from '../../triStateCheckbox';
import {FacetValueProps} from '../facet-common';
import {FacetValueExclude} from '../facet-value-exclude/facet-value-exclude';
Expand Down Expand Up @@ -51,7 +51,7 @@ export const FacetValueCheckbox: FunctionalComponent<
return <TriStateCheckbox {...attributes} state={props.state} />;
}

return <Checkbox {...attributes} checked={props.isSelected} />;
return <StencilCheckbox {...attributes} checked={props.isSelected} />;
};

const renderExclusion = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {FunctionalComponent, h} from '@stencil/core';
import Tick from '../../images/checkbox.svg';

export interface CheckboxProps {
/**
* @deprecated Should only be used for Stencil components; for Lit components, use the CheckboxProps interface from the checkbox.ts file
*/
export interface StencilCheckboxProps {
checked: boolean;
onToggle(checked: boolean): void;
key?: string | number;
Expand All @@ -16,7 +19,12 @@ export interface CheckboxProps {
onMouseDown?(evt: MouseEvent): void;
}

export const Checkbox: FunctionalComponent<CheckboxProps> = (props) => {
/**
* @deprecated Should only be used for Stencil components; for Lit components, use the Checkbox function from the checkbox.ts file
*/
export const StencilCheckbox: FunctionalComponent<StencilCheckboxProps> = (
props
) => {
const partName = props.part ?? 'checkbox';

const classNames = [
Expand Down
4 changes: 2 additions & 2 deletions packages/atomic/src/components/common/triStateCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {FacetValueState} from '@coveo/headless';
import {FunctionalComponent, h} from '@stencil/core';
import Tick from '../../images/checkbox.svg';
import Close from '../../images/close.svg';
import {CheckboxProps} from './checkbox';
import {StencilCheckboxProps} from './stencil-checkbox';

export type TriStateCheckboxProps = Omit<CheckboxProps, 'checked'> & {
export type TriStateCheckboxProps = Omit<StencilCheckboxProps, 'checked'> & {
state: FacetValueState;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import ArrowDown from '../../../../images/arrow-bottom-rounded.svg';
import ArrowUp from '../../../../images/arrow-top-rounded.svg';
import MinimizeIcon from '../../../../images/menu.svg';
import Remove from '../../../../images/remove.svg';
import {Checkbox} from '../../../common/checkbox';
import {FieldsetGroup} from '../../../common/fieldset-group';
import {IconButton} from '../../../common/iconButton';
import {StencilCheckbox} from '../../../common/stencil-checkbox';
import type {HighlightKeywords} from '../atomic-quickview-modal/atomic-quickview-modal';
import {QuickviewWordHighlight} from '../quickview-word-highlight/quickview-word-highlight';

Expand Down Expand Up @@ -80,7 +80,7 @@ const HighlightKeywordsCheckbox: FunctionalComponent<
>
> = ({i18n, highlightKeywords, onHighlightKeywords, minimized}) => (
<Fragment>
<Checkbox
<StencilCheckbox
text={i18n.t('keywords-highlight')}
class="mr-2"
id="atomic-quickview-sidebar-highlight-keywords"
Expand All @@ -91,7 +91,7 @@ const HighlightKeywordsCheckbox: FunctionalComponent<
highlightNone: !checked,
})
}
></Checkbox>
></StencilCheckbox>
{!minimized && (
<label
class="cursor-pointer whitespace-nowrap font-bold"
Expand Down
1 change: 1 addition & 0 deletions packages/atomic/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"name": "ts-lit-plugin",
"strict": true,
"rules": {
"no-missing-import": "off",
"no-unknown-property": "error",
"no-unknown-tag-name": "error",
"no-incompatible-type-binding": "error",
Expand Down

0 comments on commit 694475f

Please sign in to comment.