Skip to content

Commit

Permalink
Merge pull request #51 from primer/tooltip-eslint-rule
Browse files Browse the repository at this point in the history
Add an eslint rule for checking interactivity of tooltip trigger
  • Loading branch information
broccolinisoup authored May 26, 2023
2 parents b2ee59f + 33b5969 commit 98ff0b2
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-panthers-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'eslint-plugin-primer-react': major
---

Add `a11y-tooltip-interactive-trigger`
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ ESLint rules for Primer React
- [direct-slot-children](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/direct-slot-children.md)
- [no-deprecated-colors](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/no-deprecated-colors.md)
- [no-system-props](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/no-system-props.md)
- [a11y-tooltip-interactive-trigger](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/a11y-tooltip-interactive-trigger.md)
41 changes: 41 additions & 0 deletions docs/rules/a11y-tooltip-non-interactive-trigger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Rule Details

This rule enforces to use interactive elements as tooltip triggers. Interactive elements can be Primer `Button`, `IconButton` and `Link` components or native elements like `button`, `a` with an `href` attribute, `select`, `textarea`, `summary` and `input` (that is not a `hidden` type).

👎 Examples of **incorrect** code for this rule:

```jsx
/* eslint primer-react/a11y-tooltip-interactive-trigger: "error" */
import {Tooltip} from '@primer/react'

const App = () => (
<Tooltip text="Tooltip text">
<div>Tooltip trigger</div>
</Tooltip>
)
```

👍 Examples of **correct** code for this rule:

```jsx
/* eslint primer-react/a11y-tooltip-interactive-trigger: "error" */
import {Tooltip, Button} from '@primer/react'

const App = () => (
<Tooltip text="Supplementary text" type="description">
<Button
onClick={() => {
/* do something */
}}
>
Save
</Button>
</Tooltip>
)
```

## Options

- `skipImportCheck` (default: `false`)

By default, the `a11y-tooltip-interactive-trigger` rule will only check for interactive elements in components that are imported from `@primer/react`. You can disable this behavior by setting `skipImportCheck` to `true`. This is used for internal linting in the [primer/react](https://github.com/prime/react) repository.
3 changes: 2 additions & 1 deletion src/configs/recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ module.exports = {
rules: {
'primer-react/direct-slot-children': 'error',
'primer-react/no-deprecated-colors': 'warn',
'primer-react/no-system-props': 'warn'
'primer-react/no-system-props': 'warn',
'primer-react/a11y-tooltip-interactive-trigger': 'error'
},
settings: {
github: {
Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ module.exports = {
rules: {
'direct-slot-children': require('./rules/direct-slot-children'),
'no-deprecated-colors': require('./rules/no-deprecated-colors'),
'no-system-props': require('./rules/no-system-props')
'no-system-props': require('./rules/no-system-props'),
'a11y-tooltip-interactive-trigger': require('./rules/a11y-tooltip-interactive-trigger')
},
configs: {
recommended: require('./configs/recommended')
Expand Down
167 changes: 167 additions & 0 deletions src/rules/__tests__/a11y-tooltip-interactive-trigger.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
const rule = require('../a11y-tooltip-interactive-trigger')
const {RuleTester} = require('eslint')

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
}
})

ruleTester.run('non-interactive-tooltip-trigger', rule, {
valid: [
`import {Tooltip, Button} from '@primer/react';
<Tooltip aria-label="Filter vegetarian options" direction="e">
<Button>🥦</Button>
</Tooltip>`,

`import {Tooltip, Button} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<Button>Save</Button>
</Tooltip>`,

`import {Tooltip, IconButton} from '@primer/react';
import {SearchIcon} from '@primer/octicons-react';
<Tooltip aria-label="Supplementary text" direction="e">
<IconButton icon={SearchIcon} aria-label="Search" />
</Tooltip>`,

`import {Tooltip, Button} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<div>
<Button>Save</Button>
</div>
</Tooltip>`,

`import {Tooltip, Button} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<div>
<a href="https://gthub.com">Save</a>
</div>
</Tooltip>`,

`import {Tooltip} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<a href="https://github.com">see commit message</a>
</Tooltip>`,

`import {Tooltip, Link} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<Link href="https://github.com">Link</Link>
</Tooltip>`
],
invalid: [
{
code: `import {Tooltip} from '@primer/react';<Tooltip type="description" text="supportive text" direction="e"><button>button1</button><button>button2</button></Tooltip>
`,
errors: [
{
messageId: 'singleChild'
}
]
},
{
code: `
import {Tooltip} from '@primer/react';
<Tooltip aria-label="Filter vegetarian options" direction="e">
<span>non interactive element</span>
</Tooltip>
`,
errors: [
{
messageId: 'nonInteractiveTrigger'
}
]
},
{
code: `
import {Tooltip, Button} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<h1>Save</h1>
</Tooltip>`,
errors: [
{
messageId: 'nonInteractiveTrigger'
}
]
},
{
code: `
import {Tooltip} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<a>see commit message</a>
</Tooltip>`,
errors: [
{
messageId: 'anchorTagWithoutHref'
}
]
},
{
code: `
import {Tooltip, Link} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<Link>see commit message</Link>
</Tooltip>`,
errors: [
{
messageId: 'anchorTagWithoutHref'
}
]
},
{
code: `
import {Tooltip} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<input type="hidden" />
</Tooltip>`,
errors: [
{
messageId: 'hiddenInput'
}
]
},
{
code: `
import {Tooltip, TextInput} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<TextInput type="hidden" aria-label="Zipcode" name="zipcode" placeholder="Zipcode" autoComplete="postal-code" />
</Tooltip>`,
errors: [
{
messageId: 'hiddenInput'
}
]
},
{
code: `
import {Tooltip, Button} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<header>
<span>Save</span>
</header>
</Tooltip>`,
errors: [
{
messageId: 'nonInteractiveTrigger'
}
]
},
{
code: `import {Tooltip, Button} from '@primer/react';
<Tooltip aria-label="Supplementary text" direction="e">
<h1>
<a>Save</a>
</h1>
</Tooltip>`,
errors: [
{
messageId: 'anchorTagWithoutHref'
}
]
}
]
})
Loading

0 comments on commit 98ff0b2

Please sign in to comment.