Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tag] added TagContainer for render tags with close button #1520

Open
wants to merge 1 commit into
base: update/tag-a11y-docs
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
324 changes: 323 additions & 1 deletion semcore/tag/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { snapshot } from '@semcore/testing-utils/snapshot';
import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest';
import propsForElement from '@semcore/utils/lib/propsForElement';
import Tag from '../src';
import Tag, { TagContainer } from '../src';

import { render, fireEvent, cleanup, userEvent } from '@semcore/testing-utils/testing-library';
import { axe } from '@semcore/testing-utils/axe';
Expand Down Expand Up @@ -290,3 +290,325 @@
expect(results).toHaveNoViolations();
});
});

describe('TagContainer', () => {
beforeEach(cleanup);

const colors = [
'gray-500',
'blue-500',
'green-500',
'salad-500',
'orange-500',
'yellow-500',
'red-500',
'pink-500',
'violet-500',
];
const themes = ['primary', 'secondary'] as const;

test.concurrent('Renders correctly', async ({ task }) => {
const component = themes.flatMap((theme) =>
colors.map((color) => (
<TagContainer key={`${theme}-${color}`} theme={theme} color={color}>
<TagContainer.Tag>
<TagContainer.Tag.Text>Tag name</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
)),
);

await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('Renders without focus ring when non interactive', async ({ task }) => {
const component = (
<TagContainer>
<TagContainer.Tag id='non-interactive-tag' keyboardFocused>
<TagContainer.Tag.Text>Tag name</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
);
await expect(
await snapshot(component, {
actions: {
focus: '#non-interactive-tag',
},
}),
).toMatchImageSnapshot(task);
});

test.concurrent('Renders with focus ring when interactive', async ({ task }) => {
const component = (
<TagContainer>
<TagContainer.Tag id='interactive-tag' interactive keyboardFocused>
<TagContainer.Tag.Text>Tag name</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
);
await expect(
await snapshot(component, {
actions: {
focus: '#interactive-tag',
},
}),
).toMatchImageSnapshot(task);
});

test.concurrent('Renders correctly with keyboardFocused', async ({ task }) => {
const component = themes.flatMap((theme) =>
colors.map((color) => (
<TagContainer key={`${theme}-${color}`} theme={theme} color={color}>
<TagContainer.Tag keyboardFocused interactive>
<TagContainer.Tag.Text>Tag name</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
)),
);

await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('Renders text correctly with keyboardFocused', async ({ task }) => {
const component = themes.flatMap((theme) =>
colors.map((color) => (
<TagContainer key={`${theme}-${color}`} theme={theme} color={color}>
<TagContainer.Tag>
<TagContainer.Tag.Text>Tag name</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close keyboardFocused />
</TagContainer>
)),
);

await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('Renders correctly with Addon and Text', async ({ task }) => {
const component = (
<TagContainer>
<TagContainer.Tag>
<TagContainer.Tag.Addon id='hover'>Addon</TagContainer.Tag.Addon>
<TagContainer.Tag.Text id='hover-1'>Test</TagContainer.Tag.Text>
<TagContainer.Tag.Addon id='hover-3'>Addon</TagContainer.Tag.Addon>
</TagContainer.Tag>
</TagContainer>
);
await expect(
await snapshot(component, {
actions: {
hover: '#hover',
focus: '#hover',
},
}),
).toMatchImageSnapshot(task);
});

test.concurrent('Renders correctly with Addon as props', async ({ task }) => {
const Addon = React.forwardRef<HTMLSpanElement>((props, ref) => {
return (
<span ref={ref} {...propsForElement(props)}>
Addon prop
</span>
);
});
const component = (
<TagContainer>
<TagContainer.Tag addonLeft={Addon} addonRight={Addon}>
Test
</TagContainer.Tag>
</TagContainer>
);
await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('Should support disabled full tag', async ({ task }) => {
const component = (
<TagContainer disabled>
<TagContainer.Tag>
<TagContainer.Tag.Text>disabled</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
);

await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('Should support disabled tag content only', async ({ task }) => {
const component = (
<TagContainer>
<TagContainer.Tag disabled>
<TagContainer.Tag.Text>disabled</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
);

await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('Should support disabled close only', async ({ task }) => {
const component = (
<TagContainer>
<TagContainer.Tag>
<TagContainer.Tag.Text>enabled</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close disabled />

Check failure on line 461 in semcore/tag/__tests__/index.test.tsx

View workflow job for this annotation

GitHub Actions / static-lint

Type '{ disabled: true; }' is not assignable to type 'IntrinsicAttributes & { tag?: "div" | undefined; children?: ComponentChildren<EfficientOmit<TagCloseProps, "children"> & { children: ReactNode; }, {}, ReturnResult, never[]>; } & ComponentBasicProps<...> & { ...; } & EfficientOmit<...>'.
</TagContainer>
);

await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('Should support active', async ({ task }) => {
const component = themes.flatMap((theme) =>
colors.map((color) => (
<TagContainer key={`${theme}-${color}`} theme={theme} color={color} active>
<TagContainer.Tag>
<TagContainer.Tag.Text>Tag name</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
)),
);

await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('Should support size props', async ({ task }) => {
const component = (['xl', 'l', 'm'] as const).map((size) => (
<TagContainer size={size} key={size}>
<TagContainer.Tag>
<TagContainer.Tag.Circle>
<div style={{ width: 100, height: 100, background: 'black' }} />
</TagContainer.Tag.Circle>
<TagContainer.Tag.Text>{size}</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
));

await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('should support theme props', async ({ task }) => {
const component = themes.flatMap((theme) =>
colors.map((color) => (
<TagContainer key={`${theme}-${color}`} theme={theme} color={color}>
<TagContainer.Tag>
<TagContainer.Tag.Text>Tag name</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
)),
);
await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('should display ellipsis if text is too long', async ({ task }) => {
const component = (
<TagContainer>
<TagContainer.Tag w={80}>Lorem ipsum dolor sit amet</TagContainer.Tag>
<TagContainer.Close />
</TagContainer>
);
await expect(await snapshot(component)).toMatchImageSnapshot(task);
});

test.concurrent('should call onClick', async () => {
const onClick = vi.fn();
const { getByTestId } = render(
<TagContainer>
<TagContainer.Tag>
<TagContainer.Tag.Text>Tag</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close data-testid='close' onClick={onClick} />
</TagContainer>,
);

fireEvent.keyDown(getByTestId('close'), { key: 'Enter' });
expect(onClick).toHaveBeenCalledTimes(1);
});

test('should not call onClick with onKeydown', async () => {
const onKeyDown = vi.fn();
const onClick = vi.fn();
const { getByTestId } = render(
<TagContainer>
<TagContainer.Tag>
<TagContainer.Tag.Text>Tag</TagContainer.Tag.Text>
</TagContainer.Tag>
<TagContainer.Close data-testid='close' onClick={onClick} onKeyDown={onKeyDown} />
</TagContainer>,
);

fireEvent.keyDown(getByTestId('close'), { key: 'Enter' });
expect(onClick).toHaveBeenCalledTimes(0);
});

test('should work as Button from keyboard', async ({ expect }) => {
const onClick = vi.fn();
const { getByTestId } = render(
<TagContainer>
<TagContainer.Tag interactive onClick={onClick} data-testid={'tagAsButton'}>
some tag
</TagContainer.Tag>
</TagContainer>,
);
const tag = getByTestId('tagAsButton');
await userEvent.keyboard('[Tab]');

expect(tag).toHaveFocus();

await userEvent.keyboard('[Enter]');
expect(onClick).toHaveBeenCalledTimes(1);

await userEvent.keyboard('[Space]');
expect(onClick).toHaveBeenCalledTimes(2);
});

test('should call keydwon callback once per key down', async ({ expect }) => {
const onKeyDown = vi.fn();
const { getByTestId } = render(
<TagContainer>
<TagContainer.Tag interactive onKeyDown={onKeyDown} data-testid={'tagKeyboardTest'}>
some tag
</TagContainer.Tag>
</TagContainer>,
);
const tag = getByTestId('tagKeyboardTest');
await userEvent.keyboard('[Tab]');

expect(tag).toHaveFocus();

await userEvent.keyboard('[Enter]');
expect(onKeyDown).toHaveBeenCalledTimes(1);

await userEvent.keyboard('[Space]');
expect(onKeyDown).toHaveBeenCalledTimes(2);
});

test('a11y', async () => {
const { container } = render(
<>
<TagContainer>
<TagContainer.Tag theme='primary'>
<TagContainer.Tag.Text>primary</TagContainer.Tag.Text>
<TagContainer.Close />
</TagContainer.Tag>
</TagContainer>
<TagContainer>
<Tag>Test</Tag>
</TagContainer>
</>,
);

const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
Loading
Loading