Skip to content

Commit

Permalink
feat(SelectableCard): unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
marcinsawicki committed Dec 6, 2024
1 parent 65a8aa0 commit 562c31f
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { vi } from 'vitest';

import { render, userEvent } from 'test-utils';

import { SelectableCard } from './SelectableCard';
import { ISelectableCardProps } from './types';

const DEFAULT_PROPS = {
selectionType: 'radio' as ISelectableCardProps['selectionType'],
onClick: vi.fn(),
};

const renderComponent = (props: ISelectableCardProps) => {
return render(<SelectableCard {...props} />);
};

describe('<SelectableCard> component', () => {
it('should allow for custom class', () => {
const { container } = renderComponent({
...DEFAULT_PROPS,
className: 'my-class',
});

expect(container.firstChild).toHaveClass('my-class');
});

it('should allow for inline styles', () => {
const { container } = renderComponent({
...DEFAULT_PROPS,
style: { color: '#fff' },
});

expect(container.firstChild).toHaveStyle('color: #fff');
});

it('should render as not selected by default', () => {
const { getByRole } = renderComponent(DEFAULT_PROPS);

expect(getByRole('button')).toHaveAttribute('aria-selected', 'false');
});

it('should render as selected if isSelected is set true', () => {
const { getByRole } = renderComponent({
...DEFAULT_PROPS,
isSelected: true,
});

expect(getByRole('button')).toHaveAttribute('aria-selected', 'true');
});

it('should render card content', () => {
const { getByRole } = renderComponent({
...DEFAULT_PROPS,
children: 'Card content',
});

expect(getByRole('button')).toHaveTextContent('Card content');
});

it('should call onClick when user clicks the card', () => {
const onClick = vi.fn();
const { getByRole } = renderComponent({
...DEFAULT_PROPS,
onClick,
});

userEvent.click(getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});

it('should render as radio type', () => {
const { getByRole } = renderComponent({
...DEFAULT_PROPS,
});

expect(getByRole('radio')).toBeInTheDocument();
});

it('should render as checkbox type', () => {
const { getByRole } = renderComponent({
...DEFAULT_PROPS,
selectionType: 'checkbox',
});

expect(getByRole('checkbox')).toBeInTheDocument();
});

it('should allow to focus by tab press and call onClick on keyboard interaction', () => {
const onClick = vi.fn();
renderComponent({
...DEFAULT_PROPS,
onClick,
});

userEvent.tab();
userEvent.keyboard('{enter}');
expect(onClick).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const SelectableCard: FC<ISelectableCardProps> = ({

return (
<div
aria-selected={isSelected}
role="button"
tabIndex={0}
className={mergedClassName}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { vi } from 'vitest';

import { render } from 'test-utils';

import { ISelectableCardProps } from '../../types';

import { GallerySelectableCard } from './GallerySelectableCard';
import { IGallerySelectableCardProps } from './types';

const DEFAULT_PROPS = {
selectionType: 'radio' as ISelectableCardProps['selectionType'],
onClick: vi.fn(),
};

const renderComponent = (props: IGallerySelectableCardProps) => {
return render(<GallerySelectableCard {...props} />);
};

describe('<GallerySelectableCard> component', () => {
it('should allow for custom class for card wrapper and content', () => {
const { container, getByRole } = renderComponent({
...DEFAULT_PROPS,
className: 'my-class',
contentClassName: 'my-content-class',
});

expect(container.firstChild).toHaveClass('my-class');
expect(getByRole('presentation')).toHaveClass('my-content-class');
});

it('should render with provided icon', () => {
const { getByRole } = renderComponent({
...DEFAULT_PROPS,
icon: <div role="img" />,
});

expect(getByRole('img')).toBeInTheDocument();
});

it('should render with provided label and add extra bottom margin', () => {
const { container, getByText } = renderComponent({
...DEFAULT_PROPS,
label: 'Label',
});

expect(getByText('Label')).toBeInTheDocument();
expect(container.firstChild).toHaveStyle('margin-bottom: 29px');
});

it('should render custom element', () => {
const { getByText } = renderComponent({
...DEFAULT_PROPS,
customElement: <div>Custom element</div>,
});

expect(getByText('Custom element')).toBeInTheDocument();
});

it('should not render icon if custom element is provided', () => {
const { getByText, queryByRole } = renderComponent({
...DEFAULT_PROPS,
icon: <div role="img" />,
customElement: <div>Custom element</div>,
});

expect(queryByRole('img')).not.toBeInTheDocument();
expect(getByText('Custom element')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export const GallerySelectableCard: FC<IGallerySelectableCardProps> = ({
: {}
}
>
<div className={cx(styles[baseClass], contentClassName)}>
<div
role="presentation"
className={cx(styles[baseClass], contentClassName)}
>
{icon && !customElement && icon}
{customElement}
{label && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { vi } from 'vitest';

import { render } from 'test-utils';

import { ISelectableCardProps } from '../../types';

import { InteractiveSelectableCard } from './InteractiveSelectableCard';
import { IInteractiveSelectableCardProps } from './types';

const DEFAULT_PROPS = {
selectionType: 'radio' as ISelectableCardProps['selectionType'],
onClick: vi.fn(),
};

const renderComponent = (props: IInteractiveSelectableCardProps) => {
return render(<InteractiveSelectableCard {...props} />);
};

describe('<InteractiveSelectableCard> component', () => {
it('should allow for custom class for card wrapper and content', () => {
const { container, getByRole } = renderComponent({
...DEFAULT_PROPS,
className: 'my-class',
contentClassName: 'my-content-class',
});

expect(container.firstChild).toHaveClass('my-class');
expect(getByRole('presentation')).toHaveClass('my-content-class');
});

it('should render content', () => {
const { getByText } = renderComponent({
...DEFAULT_PROPS,
children: 'Card content',
});

expect(getByText('Card content')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ export const InteractiveSelectableCard: FC<IInteractiveSelectableCardProps> = ({
}) => {
return (
<SelectableCard {...props} kind="interactive">
<div className={cx(styles[baseClass], contentClassName)}>{children}</div>
<div
role="presentation"
className={cx(styles[baseClass], contentClassName)}
>
{children}
</div>
</SelectableCard>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { vi } from 'vitest';

import { render } from 'test-utils';

import { ISelectableCardProps } from '../../types';

import { ThumbnailSelectableCard } from './ThumbnailSelectableCard';
import { IThumbnailSelectableCardProps } from './types';

const DEFAULT_PROPS = {
label: 'Label',
selectionType: 'radio' as ISelectableCardProps['selectionType'],
onClick: vi.fn(),
};

const renderComponent = (props: IThumbnailSelectableCardProps) => {
return render(<ThumbnailSelectableCard {...props} />);
};

describe('<ThumbnailSelectableCard> component', () => {
it('should allow for custom class for card wrapper and content', () => {
const { container, getByRole } = renderComponent({
...DEFAULT_PROPS,
className: 'my-class',
contentClassName: 'my-content-class',
});

expect(container.firstChild).toHaveClass('my-class');
expect(getByRole('presentation')).toHaveClass('my-content-class');
});

it('should render with label', () => {
const { getByText } = renderComponent(DEFAULT_PROPS);

expect(getByText('Label')).toBeInTheDocument();
});

it('should render with description if provided', () => {
const { getByText } = renderComponent({
...DEFAULT_PROPS,
description: 'Description',
});

expect(getByText('Description')).toBeInTheDocument();
});

it('should render with icon if provided', () => {
const { getByRole } = renderComponent({
...DEFAULT_PROPS,
icon: <div role="img" />,
});

expect(getByRole('img')).toBeInTheDocument();
});

it('should not render label and other elements if custom element is provided', () => {
const { getByText, queryByRole, queryByText } = renderComponent({
...DEFAULT_PROPS,
description: 'Description',
icon: <div role="img" />,
customElement: <div>Custom element</div>,
});

expect(queryByRole('img')).not.toBeInTheDocument();
expect(queryByText('Label')).not.toBeInTheDocument();
expect(queryByText('Description')).not.toBeInTheDocument();
expect(getByText('Custom element')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,26 @@ export const ThumbnailSelectableCard: FC<IThumbnailSelectableCardProps> = ({
...props
}) => (
<SelectableCard {...props} kind="thumbnail">
<div className={cx(styles[baseClass], contentClassName)}>
{icon && <div className={styles[`${baseClass}__icon`]}>{icon}</div>}
<div
role="presentation"
className={cx(styles[baseClass], contentClassName)}
>
{!customElement && (
<div className={styles[`${baseClass}__content`]}>
<Text as="span" className={styles[`${baseClass}__content__label`]}>
{label}
</Text>
<Text
size="sm"
as="span"
className={styles[`${baseClass}__content__description`]}
>
{description}
</Text>
</div>
<>
{icon && <div className={styles[`${baseClass}__icon`]}>{icon}</div>}
<div className={styles[`${baseClass}__content`]}>
<Text as="span" className={styles[`${baseClass}__content__label`]}>
{label}
</Text>
<Text
size="sm"
as="span"
className={styles[`${baseClass}__content__description`]}
>
{description}
</Text>
</div>
</>
)}
{customElement}
</div>
Expand Down

0 comments on commit 562c31f

Please sign in to comment.