refactor: Fix anchor-is-valid lint warnings (#12010)

* Fix anchor-is-valid lint warnings

* Change IconTooltip to use named exports
This commit is contained in:
Michael S. Molina 2020-12-22 04:58:05 -03:00 committed by GitHub
parent e2f676445b
commit 8682c6fc1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 115 additions and 80 deletions

View File

@ -86,7 +86,7 @@ describe('ChangeDatasourceModal', () => {
it('renders confirmation message', async () => { it('renders confirmation message', async () => {
act(() => { act(() => {
wrapper.find('.datasource-link').at(0).props().onClick(); wrapper.find('[data-test="datasource-link"]').at(0).props().onClick();
}); });
await waitForComponentToPaint(wrapper); await waitForComponentToPaint(wrapper);
@ -95,7 +95,7 @@ describe('ChangeDatasourceModal', () => {
it('changes the datasource', async () => { it('changes the datasource', async () => {
act(() => { act(() => {
wrapper.find('.datasource-link').at(0).props().onClick(); wrapper.find('[data-test="datasource-link"]').at(0).props().onClick();
}); });
await waitForComponentToPaint(wrapper); await waitForComponentToPaint(wrapper);

View File

@ -18,22 +18,23 @@
*/ */
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { Tooltip } from 'src/common/components/Tooltip';
import { IconTooltip } from 'src/components/IconTooltip';
import Link from 'src/components/Link'; describe('IconTooltip', () => {
describe('Link', () => {
const mockedProps = { const mockedProps = {
tooltip: 'This is a tooltip', tooltip: 'This is a tooltip',
href: 'https://www.airbnb.com',
}; };
it('renders', () => { it('renders', () => {
expect(React.isValidElement(<Link>TEST</Link>)).toBe(true); expect(React.isValidElement(<IconTooltip>TEST</IconTooltip>)).toBe(true);
}); });
it('renders with props', () => { it('renders with props', () => {
expect(React.isValidElement(<Link {...mockedProps}>TEST</Link>)).toBe(true); expect(
React.isValidElement(<IconTooltip {...mockedProps}>TEST</IconTooltip>),
).toBe(true);
}); });
it('renders an anchor tag', () => { it('renders a tooltip', () => {
const wrapper = shallow(<Link {...mockedProps}>TEST</Link>); const wrapper = shallow(<IconTooltip {...mockedProps}>TEST</IconTooltip>);
expect(wrapper.find('a')).toExist(); expect(wrapper.find(Tooltip)).toExist();
}); });
}); });

View File

@ -22,7 +22,7 @@ import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store'; import configureStore from 'redux-mock-store';
import { supersetTheme, ThemeProvider } from '@superset-ui/core'; import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import Link from 'src/components/Link'; import { IconTooltip } from 'src/components/IconTooltip';
import Fade from 'src/common/components/Fade'; import Fade from 'src/common/components/Fade';
import TableElement from 'src/SqlLab/components/TableElement'; import TableElement from 'src/SqlLab/components/TableElement';
import ColumnElement from 'src/SqlLab/components/ColumnElement'; import ColumnElement from 'src/SqlLab/components/ColumnElement';
@ -43,9 +43,9 @@ describe('TableElement', () => {
it('renders with props', () => { it('renders with props', () => {
expect(React.isValidElement(<TableElement {...mockedProps} />)).toBe(true); expect(React.isValidElement(<TableElement {...mockedProps} />)).toBe(true);
}); });
it('has 2 Link elements', () => { it('has 2 IconTooltip elements', () => {
const wrapper = shallow(<TableElement {...mockedProps} />); const wrapper = shallow(<TableElement {...mockedProps} />);
expect(wrapper.find(Link)).toHaveLength(2); expect(wrapper.find(IconTooltip)).toHaveLength(2);
}); });
it('has 14 columns', () => { it('has 14 columns', () => {
const wrapper = shallow(<TableElement {...mockedProps} />); const wrapper = shallow(<TableElement {...mockedProps} />);
@ -95,7 +95,7 @@ describe('TableElement', () => {
}, },
); );
expect(mockedActions.collapseTable.called).toBe(false); expect(mockedActions.collapseTable.called).toBe(false);
wrapper.find('.table-name').simulate('click'); wrapper.find('[data-test="collapse"]').hostNodes().simulate('click');
expect(mockedActions.collapseTable.called).toBe(true); expect(mockedActions.collapseTable.called).toBe(true);
}); });
it('removes the table', () => { it('removes the table', () => {

View File

@ -27,7 +27,7 @@ import { t } from '@superset-ui/core';
import TableView from 'src/components/TableView'; import TableView from 'src/components/TableView';
import Button from 'src/components/Button'; import Button from 'src/components/Button';
import { fDuration } from 'src/modules/dates'; import { fDuration } from 'src/modules/dates';
import Link from '../../components/Link'; import { IconTooltip } from '../../components/IconTooltip';
import ResultSet from './ResultSet'; import ResultSet from './ResultSet';
import ModalTrigger from '../../components/ModalTrigger'; import ModalTrigger from '../../components/ModalTrigger';
import HighlightedSql from './HighlightedSql'; import HighlightedSql from './HighlightedSql';
@ -175,9 +175,9 @@ const QueryTable = props => {
let errorTooltip; let errorTooltip;
if (q.errorMessage) { if (q.errorMessage) {
errorTooltip = ( errorTooltip = (
<Link tooltip={q.errorMessage}> <IconTooltip tooltip={q.errorMessage}>
<i className="fa fa-exclamation-circle text-danger" /> <i className="fa fa-exclamation-circle text-danger" />
</Link> </IconTooltip>
); );
} }
q.state = ( q.state = (
@ -188,22 +188,22 @@ const QueryTable = props => {
); );
q.actions = ( q.actions = (
<div> <div>
<Link <IconTooltip
className="fa fa-pencil m-r-3" className="fa fa-pencil m-r-3 pointer"
onClick={() => restoreSql(query)} onClick={() => restoreSql(query)}
tooltip={t( tooltip={t(
'Overwrite text in the editor with a query on this table', 'Overwrite text in the editor with a query on this table',
)} )}
placement="top" placement="top"
/> />
<Link <IconTooltip
className="fa fa-plus-circle m-r-3" className="fa fa-plus-circle m-r-3 pointer"
onClick={() => openQueryInNewTab(query)} onClick={() => openQueryInNewTab(query)}
tooltip={t('Run query in a new tab')} tooltip={t('Run query in a new tab')}
placement="top" placement="top"
/> />
<Link <IconTooltip
className="fa fa-trash m-r-3" className="fa fa-trash m-r-3 pointer"
tooltip={t('Remove query from log')} tooltip={t('Remove query from log')}
onClick={() => removeQuery(query)} onClick={() => removeQuery(query)}
/> />

View File

@ -21,7 +21,7 @@ import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light';
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'; import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql';
import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github'; import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github';
import Link from '../../components/Link'; import { IconTooltip } from '../../components/IconTooltip';
import ModalTrigger from '../../components/ModalTrigger'; import ModalTrigger from '../../components/ModalTrigger';
SyntaxHighlighter.registerLanguage('sql', sql); SyntaxHighlighter.registerLanguage('sql', sql);
@ -41,10 +41,9 @@ export default function ShowSQL({
<ModalTrigger <ModalTrigger
modalTitle={title} modalTitle={title}
triggerNode={ triggerNode={
<Link <IconTooltip
className="fa fa-eye pull-left m-l-2" className="fa fa-eye pull-left m-l-2"
tooltip={tooltipText} tooltip={tooltipText}
href="#"
/> />
} }
modalBody={ modalBody={

View File

@ -20,12 +20,12 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ButtonGroup, Collapse, Well } from 'react-bootstrap'; import { ButtonGroup, Collapse, Well } from 'react-bootstrap';
import shortid from 'shortid'; import shortid from 'shortid';
import { t } from '@superset-ui/core'; import { t, styled } from '@superset-ui/core';
import Fade from 'src/common/components/Fade'; import Fade from 'src/common/components/Fade';
import { Tooltip } from 'src/common/components/Tooltip'; import { Tooltip } from 'src/common/components/Tooltip';
import CopyToClipboard from '../../components/CopyToClipboard'; import CopyToClipboard from '../../components/CopyToClipboard';
import Link from '../../components/Link'; import { IconTooltip } from '../../components/IconTooltip';
import ColumnElement from './ColumnElement'; import ColumnElement from './ColumnElement';
import ShowSQL from './ShowSQL'; import ShowSQL from './ShowSQL';
import ModalTrigger from '../../components/ModalTrigger'; import ModalTrigger from '../../components/ModalTrigger';
@ -43,6 +43,14 @@ const defaultProps = {
timeout: 500, timeout: 500,
}; };
const StyledSpan = styled.span`
color: ${({ theme }) => theme.colors.primary.dark1};
&: hover {
color: ${({ theme }) => theme.colors.primary.dark2};
}
cursor: pointer;
`;
class TableElement extends React.PureComponent { class TableElement extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
@ -145,7 +153,7 @@ class TableElement extends React.PureComponent {
<pre key={i}>{JSON.stringify(ix, null, ' ')}</pre> <pre key={i}>{JSON.stringify(ix, null, ' ')}</pre>
))} ))}
triggerNode={ triggerNode={
<Link <IconTooltip
className="fa fa-key pull-left m-l-2" className="fa fa-key pull-left m-l-2"
tooltip={t('View keys & indexes (%s)', table.indexes.length)} tooltip={t('View keys & indexes (%s)', table.indexes.length)}
/> />
@ -156,10 +164,10 @@ class TableElement extends React.PureComponent {
return ( return (
<ButtonGroup className="ws-el-controls"> <ButtonGroup className="ws-el-controls">
{keyLink} {keyLink}
<Link <IconTooltip
className={ className={
`fa fa-sort-${!this.state.sortColumns ? 'alpha' : 'numeric'}-asc ` + `fa fa-sort-${!this.state.sortColumns ? 'alpha' : 'numeric'}-asc ` +
'pull-left sort-cols m-l-2' 'pull-left sort-cols m-l-2 pointer'
} }
onClick={this.toggleSortColumns} onClick={this.toggleSortColumns}
tooltip={ tooltip={
@ -167,14 +175,13 @@ class TableElement extends React.PureComponent {
? t('Sort columns alphabetically') ? t('Sort columns alphabetically')
: t('Original table column order') : t('Original table column order')
} }
href="#"
/> />
{table.selectStar && ( {table.selectStar && (
<CopyToClipboard <CopyToClipboard
copyNode={ copyNode={
<a aria-label="Copy"> <IconTooltip aria-label="Copy">
<i aria-hidden className="fa fa-clipboard pull-left m-l-2" /> <i aria-hidden className="fa fa-clipboard pull-left m-l-2" />
</a> </IconTooltip>
} }
text={table.selectStar} text={table.selectStar}
shouldShowText={false} shouldShowText={false}
@ -188,11 +195,10 @@ class TableElement extends React.PureComponent {
title={t('CREATE VIEW statement')} title={t('CREATE VIEW statement')}
/> />
)} )}
<Link <IconTooltip
className="fa fa-times table-remove pull-left m-l-2" className="fa fa-times table-remove pull-left m-l-2 pointer"
onClick={this.removeTable} onClick={this.removeTable}
tooltip={t('Remove table preview')} tooltip={t('Remove table preview')}
href="#"
/> />
</ButtonGroup> </ButtonGroup>
); );
@ -209,15 +215,15 @@ class TableElement extends React.PureComponent {
title={table.name} title={table.name}
trigger={['hover']} trigger={['hover']}
> >
<a <StyledSpan
href="#" data-test="collapse"
className="table-name" className="table-name"
onClick={e => { onClick={e => {
this.toggleTable(e); this.toggleTable(e);
}} }}
> >
<strong>{table.name}</strong> <strong>{table.name}</strong>
</a> </StyledSpan>
</Tooltip> </Tooltip>
<div className="pull-right header-right-side"> <div className="pull-right header-right-side">

View File

@ -114,9 +114,14 @@ export default function ErrorAlert({
<strong>{title}</strong> <strong>{title}</strong>
</LeftSideContent> </LeftSideContent>
{!isExpandable && ( {!isExpandable && (
<a href="#" className="link" onClick={() => setIsModalOpen(true)}> <span
role="button"
tabIndex={0}
className="link"
onClick={() => setIsModalOpen(true)}
>
{t('See More')} {t('See More')}
</a> </span>
)} )}
</div> </div>
{isExpandable ? ( {isExpandable ? (
@ -125,25 +130,27 @@ export default function ErrorAlert({
{body && ( {body && (
<> <>
{!isBodyExpanded && ( {!isBodyExpanded && (
<a <span
href="#" role="button"
tabIndex={0}
className="link" className="link"
onClick={() => setIsBodyExpanded(true)} onClick={() => setIsBodyExpanded(true)}
> >
{t('See More')} {t('See More')}
</a> </span>
)} )}
{isBodyExpanded && ( {isBodyExpanded && (
<> <>
<br /> <br />
{body} {body}
<a <span
href="#" role="button"
tabIndex={0}
className="link" className="link"
onClick={() => setIsBodyExpanded(false)} onClick={() => setIsBodyExpanded(false)}
> >
{t('See Less')} {t('See Less')}
</a> </span>
</> </>
)} )}
</> </>

View File

@ -18,11 +18,11 @@
*/ */
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { Tooltip } from 'src/common/components/Tooltip'; import { Tooltip } from 'src/common/components/Tooltip';
import { styled } from '@superset-ui/core';
interface Props { interface Props {
children?: ReactNode; children?: ReactNode;
className?: string; className?: string;
href?: string;
onClick?: () => void; onClick?: () => void;
placement?: placement?:
| 'bottom' | 'bottom'
@ -41,24 +41,29 @@ interface Props {
tooltip?: string | null; tooltip?: string | null;
} }
const Link = ({ const StyledSpan = styled.span`
color: ${({ theme }) => theme.colors.primary.dark1};
&: hover {
color: ${({ theme }) => theme.colors.primary.dark2};
}
`;
const IconTooltip = ({
children = null, children = null,
className = '', className = '',
href = '#',
onClick = () => undefined, onClick = () => undefined,
placement = 'top', placement = 'top',
style = {}, style = {},
tooltip = null, tooltip = null,
}: Props) => { }: Props) => {
const link = ( const iconTooltip = (
<a <StyledSpan
href={href}
onClick={onClick} onClick={onClick}
style={style} style={style}
className={`Link ${className}`} className={`IconTooltip ${className}`}
> >
{children} {children}
</a> </StyledSpan>
); );
if (tooltip) { if (tooltip) {
return ( return (
@ -69,11 +74,11 @@ const Link = ({
mouseEnterDelay={0.3} mouseEnterDelay={0.3}
mouseLeaveDelay={0.15} mouseLeaveDelay={0.15}
> >
{link} {iconTooltip}
</Tooltip> </Tooltip>
); );
} }
return link; return iconTooltip;
}; };
export default Link; export { IconTooltip };

View File

@ -265,9 +265,9 @@ class SliceHeaderControls extends React.PureComponent {
triggerNode.closest(SCREENSHOT_NODE_SELECTOR) triggerNode.closest(SCREENSHOT_NODE_SELECTOR)
} }
> >
<a id={`slice_${slice.slice_id}-controls`} role="button"> <span id={`slice_${slice.slice_id}-controls`} role="button">
<VerticalDotsTrigger /> <VerticalDotsTrigger />
</a> </span>
</NoAnimationDropdown> </NoAnimationDropdown>
); );
} }

View File

@ -29,13 +29,13 @@ const propTypes = {
export default function FilterFieldItem({ label, isSelected }) { export default function FilterFieldItem({ label, isSelected }) {
return ( return (
<a <span
className={cx('filter-field-item filter-container', { className={cx('filter-field-item filter-container', {
'is-selected': isSelected, 'is-selected': isSelected,
})} })}
> >
<FormLabel htmlFor={label}>{label}</FormLabel> <FormLabel htmlFor={label}>{label}</FormLabel>
</a> </span>
); );
} }

View File

@ -35,7 +35,7 @@ function traverse({ currentNode = {}, selectedChartId }) {
return { return {
...currentNode, ...currentNode,
label: ( label: (
<a <span
className={cx(`filter-scope-type ${type.toLowerCase()}`, { className={cx(`filter-scope-type ${type.toLowerCase()}`, {
'selected-filter': selectedChartId === value, 'selected-filter': selectedChartId === value,
})} })}
@ -46,7 +46,7 @@ function traverse({ currentNode = {}, selectedChartId }) {
</span> </span>
)} )}
{label} {label}
</a> </span>
), ),
children: updatedChildren, children: updatedChildren,
}; };
@ -54,13 +54,13 @@ function traverse({ currentNode = {}, selectedChartId }) {
return { return {
...currentNode, ...currentNode,
label: ( label: (
<a <span
className={cx(`filter-scope-type ${type.toLowerCase()}`, { className={cx(`filter-scope-type ${type.toLowerCase()}`, {
'selected-filter': selectedChartId === value, 'selected-filter': selectedChartId === value,
})} })}
> >
{label} {label}
</a> </span>
), ),
}; };
} }

View File

@ -48,6 +48,14 @@ const StyledForm = styled(Form)`
width: 100%; width: 100%;
`; `;
const StyledSpan = styled.span`
cursor: pointer;
color: ${({ theme }) => theme.colors.primary.dark1};
&: hover {
color: ${({ theme }) => theme.colors.primary.dark2};
}
`;
const FilterTabs = styled(LineEditableTabs)` const FilterTabs = styled(LineEditableTabs)`
// extra selector specificity: // extra selector specificity:
&.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab { &.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab {
@ -473,13 +481,13 @@ export function FilterConfigModal({
: getFilterTitle(id)} : getFilterTitle(id)}
</div> </div>
{removedFilters[id] && ( {removedFilters[id] && (
<a <StyledSpan
role="button" role="button"
tabIndex={0} tabIndex={0}
onClick={() => restoreFilter(id)} onClick={() => restoreFilter(id)}
> >
{t('Undo?')} {t('Undo?')}
</a> </StyledSpan>
)} )}
</FilterTabTitle> </FilterTabTitle>
} }

View File

@ -67,6 +67,14 @@ const ConfirmModalStyled = styled.div`
} }
`; `;
const StyledSpan = styled.span`
cursor: pointer;
color: ${({ theme }) => theme.colors.primary.dark1};
&: hover {
color: ${({ theme }) => theme.colors.primary.dark2};
}
`;
const TABLE_COLUMNS = [ const TABLE_COLUMNS = [
'name', 'name',
'type', 'type',
@ -191,13 +199,14 @@ const ChangeDatasourceModal: FunctionComponent<ChangeDatasourceModalProps> = ({
connection: ds.database.database_name, connection: ds.database.database_name,
schema: ds.schema, schema: ds.schema,
name: ( name: (
<a <StyledSpan
href="#" role="button"
tabIndex={0}
data-test="datasource-link"
onClick={() => selectDatasource({ type: 'table', ...ds })} onClick={() => selectDatasource({ type: 'table', ...ds })}
className="datasource-link"
> >
{ds.table_name} {ds.table_name}
</a> </StyledSpan>
), ),
type: ds.kind, type: ds.kind,
})); }));

View File

@ -790,12 +790,12 @@ class DatasourceEditor extends React.PureComponent {
</Fieldset> </Fieldset>
{this.allowEditSource && ( {this.allowEditSource && (
<EditLockContainer> <EditLockContainer>
<a href="#" onClick={this.onChangeEditMode}> <span role="button" tabIndex={0} onClick={this.onChangeEditMode}>
<Icon <Icon
color={supersetTheme.colors.grayscale.base} color={supersetTheme.colors.grayscale.base}
name={this.state.isEditMode ? 'lock-unlocked' : 'lock-locked'} name={this.state.isEditMode ? 'lock-unlocked' : 'lock-locked'}
/> />
</a> </span>
{!this.state.isEditMode && ( {!this.state.isEditMode && (
<div>{t('Click the lock to make changes.')}</div> <div>{t('Click the lock to make changes.')}</div>
)} )}

View File

@ -79,7 +79,7 @@ export default function ExploreActionButtons({
)} )}
{latestQueryFormData && ( {latestQueryFormData && (
<a <div
role="button" role="button"
tabIndex={0} tabIndex={0}
onClick={doExportChart} onClick={doExportChart}
@ -89,10 +89,10 @@ export default function ExploreActionButtons({
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i className="fa fa-file-code-o" /> .json <i className="fa fa-file-code-o" /> .json
</a> </div>
)} )}
{latestQueryFormData && ( {latestQueryFormData && (
<a <div
role="button" role="button"
tabIndex={0} tabIndex={0}
onClick={doExportCSV} onClick={doExportCSV}
@ -102,7 +102,7 @@ export default function ExploreActionButtons({
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i className="fa fa-file-text-o" /> .csv <i className="fa fa-file-text-o" /> .csv
</a> </div>
)} )}
<ConnectedDisplayQueryButton <ConnectedDisplayQueryButton
chartHeight={chartHeight} chartHeight={chartHeight}