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

Feature/axe core #46

Merged
merged 3 commits into from
Sep 24, 2023
Merged
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).

## [Unreleased]
### Added
* [#45](https://github.com/shlinkio/shlink-frontend-kit/issues/45) Add a11y tests with axe-core.

### Changed
* *Nothing*

### Deprecated
* *Nothing*

### Removed
* *Nothing*

### Fixed
* *Nothing*


## [0.3.0] - 2023-09-05
### Added
* Add `useToggleTimeout` hook.
Expand Down
48 changes: 43 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@types/uuid": "^9.0.4",
"@vitejs/plugin-react": "^4.0.4",
"@vitest/coverage-v8": "^0.34.5",
"axe-core": "^4.8.2",
"bootstrap": "5.2.3",
"eslint": "^8.50.0",
"jsdom": "^22.1.0",
Expand All @@ -66,7 +67,8 @@
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-dts": "^3.5.4",
"vitest": "^0.34.5"
"vitest": "^0.34.5",
"vitest-canvas-mock": "^0.3.3"
},
"browserslist": [
">0.2%",
Expand Down
2 changes: 1 addition & 1 deletion src/block/SimpleCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type SimpleCardProps = Omit<CardProps, 'title'> & {

export const SimpleCard = ({ title, children, bodyClassName, ...rest }: SimpleCardProps) => (
<Card {...rest}>
{title && <CardHeader role="heading">{title}</CardHeader>}
{title && <CardHeader role="heading" aria-level={4}>{title}</CardHeader>}
<CardBody className={bodyClassName}>{children}</CardBody>
</Card>
);
2 changes: 1 addition & 1 deletion src/form/InputFormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const InputFormGroup: FC<InputFormGroupProps> = (
const id = useDomId();

return (
<LabeledFormGroup label={<>{children}:</>} className={className ?? ''} labelClassName={labelClassName} id={id}>
<LabeledFormGroup label={<>{children}:</>} className={className} labelClassName={labelClassName} id={id}>
<input
id={id}
className="form-control"
Expand Down
3 changes: 2 additions & 1 deletion src/navigation/DropdownBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const DropdownBtn: FC<DropdownBtnProps> = ({
minWidth,
inline,
size,
...rest
}) => {
const [isOpen, toggle] = useToggle();
const toggleClasses = classNames('dropdown-btn__toggle', className, {
Expand All @@ -36,7 +37,7 @@ export const DropdownBtn: FC<DropdownBtnProps> = ({

return (
<Dropdown isOpen={isOpen} toggle={toggle} disabled={disabled} className={dropdownClassName}>
<DropdownToggle size={size} caret={!noCaret} className={toggleClasses} color="primary">
<DropdownToggle size={size} caret={!noCaret} className={toggleClasses} color="primary" {...rest}>
{text}
</DropdownToggle>
<DropdownMenu className="w-100" end={end} style={menuStyle}>{children}</DropdownMenu>
Expand Down
10 changes: 6 additions & 4 deletions src/navigation/NavPills.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FC, PropsWithChildren } from 'react';
import { Children, isValidElement } from 'react';
import { NavLink as RouterNavLink } from 'react-router-dom';
import { Card, Nav, NavLink } from 'reactstrap';
import { Card, Nav, NavItem, NavLink } from 'reactstrap';
import './NavPills.scss';

type NavPillsProps = PropsWithChildren<{
Expand All @@ -15,9 +15,11 @@ type NavPillItemProps = PropsWithChildren<{
}>;

export const NavPillItem: FC<NavPillItemProps> = ({ children, ...rest }) => (
<NavLink className="nav-pills__nav-link" tag={RouterNavLink} {...rest}>
{children}
</NavLink>
<NavItem>
<NavLink className="nav-pills__nav-link" tag={RouterNavLink} {...rest}>
{children}
</NavLink>
</NavItem>
);

export const NavPills: FC<NavPillsProps> = ({ children, fill = false, className = '' }) => (
Expand Down
6 changes: 5 additions & 1 deletion src/navigation/RowDropdownBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import { DropdownBtn } from './DropdownBtn';

export type DropdownBtnMenuProps = PropsWithChildren<{
minWidth?: number;

/** Accessible label for screen readers. Defaults to "Options" */
label?: string;
}>;

export const RowDropdownBtn: FC<DropdownBtnMenuProps> = ({ children, minWidth }) => (
export const RowDropdownBtn: FC<DropdownBtnMenuProps> = ({ children, minWidth, label = 'Options' }) => (
<DropdownBtn
text={<FontAwesomeIcon className="px-1" icon={menuIcon} />}
aria-label={label}
size="sm"
minWidth={minWidth}
end
Expand Down
6 changes: 6 additions & 0 deletions test/__helpers__/accessibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { run } from 'axe-core';

export const checkAccessibility = async ({ container }: { container: HTMLElement }) => {
const result = await run(container);
expect(result.violations).toStrictEqual([]);
};
12 changes: 9 additions & 3 deletions test/block/Message.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ import { render, screen } from '@testing-library/react';
import type { PropsWithChildren } from 'react';
import type { MessageProps } from '../../src';
import { Message } from '../../src';
import { checkAccessibility } from '../__helpers__/accessibility';

describe('<Message />', () => {
const setUp = (props: PropsWithChildren<MessageProps> = {}) => render(<Message {...props} />);

it.each([
[{ loading: true }],
[{ children: 'Something is wrong' }],
])('passes a11y checks', (props: MessageProps) => checkAccessibility(setUp(props)));

it.each([
[true, 'col-md-12'],
[false, 'col-md-10 offset-md-1'],
Expand Down Expand Up @@ -33,11 +39,11 @@ describe('<Message />', () => {
});

it.each([
['error', 'border-danger', 'text-danger'],
['default', '', 'text-muted'],
['error' as const, 'border-danger', 'text-danger'],
['default' as const, '', 'text-muted'],
[undefined, '', 'text-muted'],
])('renders proper classes based on message type', (type, expectedCardClass, expectedH3Class) => {
const { container } = setUp({ type: type as 'default' | 'error' | undefined });
const { container } = setUp({ type });

expect(container.querySelector('.card-body')).toHaveAttribute('class', expect.stringContaining(expectedCardClass));
expect(screen.getByRole('heading')).toHaveClass(`text-center mb-0 ${expectedH3Class}`);
Expand Down
15 changes: 11 additions & 4 deletions test/block/Result.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { render, screen } from '@testing-library/react';
import type { ResultProps, ResultType } from '../../src';
import type { ResultProps } from '../../src';
import { Result } from '../../src';
import { checkAccessibility } from '../__helpers__/accessibility';

describe('<Result />', () => {
const setUp = (props: ResultProps) => render(<Result {...props} />);

it.each([
['success' as ResultType, 'bg-main text-white'],
['error' as ResultType, 'bg-danger text-white'],
['warning' as ResultType, 'bg-warning'],
['success' as const],
['error' as const],
['warning' as const],
])('passes a11y checks', (type) => checkAccessibility(setUp({ children: 'The result', type })));

it.each([
['success' as const, 'bg-main text-white'],
['error' as const, 'bg-danger text-white'],
['warning' as const, 'bg-warning'],
])('renders expected classes based on type', (type, expectedClasses) => {
setUp({ type });
expect(screen.getByRole('document')).toHaveClass(expectedClasses);
Expand Down
7 changes: 5 additions & 2 deletions test/block/SimpleCard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { render, screen } from '@testing-library/react';
import type { SimpleCardProps } from '../../src';
import { SimpleCard } from '../../src';

const setUp = ({ children, ...rest }: SimpleCardProps = {}) => render(<SimpleCard {...rest}>{children}</SimpleCard>);
import { checkAccessibility } from '../__helpers__/accessibility';

describe('<SimpleCard />', () => {
const setUp = ({ children, ...rest }: SimpleCardProps = {}) => render(<SimpleCard {...rest}>{children}</SimpleCard>);

it('passes a11y checks', () => checkAccessibility(setUp({ children: 'Foo', title: 'Bar' })));

it('does not render title if not provided', () => {
setUp();
expect(screen.queryByRole('heading')).not.toBeInTheDocument();
Expand Down
3 changes: 3 additions & 0 deletions test/form/Checkbox.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { render, screen } from '@testing-library/react';
import { Checkbox } from '../../src';
import { checkAccessibility } from '../__helpers__/accessibility';
import { renderWithEvents } from '../__helpers__/setUpTest';

describe('<Checkbox />', () => {
it('passes a11y checks', () => checkAccessibility(render(<Checkbox>Hi!</Checkbox>)));

it.each([['foo'], ['bar'], ['baz']])('includes extra class names when provided', (className) => {
const { container } = render(<Checkbox className={className} />);
expect(container.firstChild).toHaveAttribute('class', `form-check form-checkbox ${className}`);
Expand Down
10 changes: 6 additions & 4 deletions test/form/InputFormGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { fireEvent, screen } from '@testing-library/react';
import type { InputType } from 'reactstrap/types/lib/Input';
import type { InputFormGroupProps } from '../../src';
import { InputFormGroup } from '../../src';
import { checkAccessibility } from '../__helpers__/accessibility';
import { renderWithEvents } from '../__helpers__/setUpTest';

describe('<InputFormGroup />', () => {
Expand All @@ -10,16 +10,18 @@ describe('<InputFormGroup />', () => {
<InputFormGroup value="foo" {...props} onChange={onChange} placeholder="The input" />,
);

it('passes a11y checks', () => checkAccessibility(setUp({ children: 'The label' })));

it('renders input with placeholder', () => {
setUp();
expect(screen.getByPlaceholderText('The input')).toBeInTheDocument();
});

it.each([
[undefined, 'text'],
['text' as InputType, 'text'],
['email' as InputType, 'email'],
['date' as InputType, 'date'],
['text' as const, 'text'],
['email' as const, 'email'],
['date' as const, 'date'],
])('renders input with correct type', (type, expectedType) => {
setUp({ type, children: 'The label' });
expect(screen.getByLabelText('The label:')).toHaveAttribute('type', expectedType);
Expand Down
3 changes: 3 additions & 0 deletions test/form/SearchField.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';
import type { ComponentProps } from 'react';
import { SearchField } from '../../src';
import { checkAccessibility } from '../__helpers__/accessibility';
import { renderWithEvents } from '../__helpers__/setUpTest';

type SearchFieldProps = ComponentProps<typeof SearchField>;
Expand All @@ -11,6 +12,8 @@ describe('<SearchField />', () => {
<SearchField onChange={onChange} {...props} />,
);

it('passes a11y checks', () => checkAccessibility(setUp()));

it('displays clear button when value is not empty', () => {
setUp({ initialValue: 'Foo' });
expect(screen.getByLabelText('Clear search')).not.toHaveAttribute('hidden');
Expand Down
3 changes: 3 additions & 0 deletions test/navigation/DropdownBtn.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { screen } from '@testing-library/react';
import type { PropsWithChildren } from 'react';
import type { DropdownBtnProps } from '../../src';
import { DropdownBtn } from '../../src';
import { checkAccessibility } from '../__helpers__/accessibility';
import { renderWithEvents } from '../__helpers__/setUpTest';

describe('<DropdownBtn />', () => {
const setUp = (props: PropsWithChildren<DropdownBtnProps>) => renderWithEvents(
<DropdownBtn children="foo" {...props} />,
);

it('passes a11y checks', () => checkAccessibility(setUp({ text: 'Menu' })));

it.each([['foo'], ['bar'], ['baz']])('displays provided text in button', (text) => {
setUp({ text });
expect(screen.getByRole('button')).toHaveTextContent(text);
Expand Down
Loading