Add support for List item tags and link types (https://github.com/woocommerce/woocommerce-admin/pull/4287)

* Add Storybook console addon.
* Add Link stories for all link types.
* Add unit tests for Link component.
* Add unit tests for List component.
* Add support for List item `listItemTag`.
This commit is contained in:
Matt Sherman 2020-05-07 11:52:49 -04:00 committed by GitHub
parent 015fc89dbd
commit 61a643655a
7 changed files with 378 additions and 8 deletions

View File

@ -3646,6 +3646,15 @@
}
}
},
"@storybook/addon-console": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@storybook/addon-console/-/addon-console-1.2.1.tgz",
"integrity": "sha512-2iDbDTipWonvRpIqLLntfhCGvawFFvoG1xyErpyL7K/HRdQ1zzIvR1Qm83S7TK8Vg+RzZWm4wcDbxx7WOsFCNg==",
"dev": true,
"requires": {
"global": "^4.3.2"
}
},
"@storybook/addon-docs": {
"version": "5.3.18",
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-5.3.18.tgz",
@ -5313,6 +5322,12 @@
"@types/testing-library__react": "^10.0.0"
}
},
"@testing-library/user-event": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-10.1.0.tgz",
"integrity": "sha512-qutUm/2lWAD8IiKrss2Cg6Hf8AkcMeylKm09bSMtYC39Ug68aXWkcbc0H/NVD5R1zOHguTjkR/Ppuns6bWksGQ==",
"dev": true
},
"@types/anymatch": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@ -22203,7 +22218,7 @@
"dev": true
},
"prettier": {
"version": "npm:wp-prettier@1.19.1",
"version": "npm:prettier@1.19.1",
"resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-1.19.1.tgz",
"integrity": "sha512-mqAC2r1NDmRjG+z3KCJ/i61tycKlmADIjxnDhQab+KBxSAGbF/W7/zwB2guy/ypIeKrrftNsIYkNZZQKf3vJcg==",
"dev": true

View File

@ -145,6 +145,7 @@
"@octokit/graphql": "4.3.1",
"@storybook/addon-a11y": "5.3.18",
"@storybook/addon-actions": "5.3.18",
"@storybook/addon-console": "1.2.1",
"@storybook/addon-docs": "5.3.18",
"@storybook/addon-knobs": "5.3.18",
"@storybook/addon-links": "5.3.18",
@ -153,6 +154,7 @@
"@storybook/addons": "5.3.18",
"@storybook/react": "5.3.18",
"@testing-library/react": "^10.0.3",
"@testing-library/user-event": "^10.1.0",
"@wordpress/babel-plugin-import-jsx-pragma": "1.1.3",
"@wordpress/babel-plugin-makepot": "2.1.3",
"@wordpress/babel-preset-default": "3.0.2",

View File

@ -1,17 +1,62 @@
/*
/**
* External dependencies
*/
import { withConsole } from '@storybook/addon-console';
/**
* Internal dependencies
*/
import Link from '../';
function logLinkClick( event ) {
const a = event.currentTarget;
const logMessage = `[${ a.textContent }](${ a.href }) ${ a.dataset.linkType } link clicked`;
// eslint-disable-next-line no-console
console.log( logMessage );
event.preventDefault();
return false;
}
export default {
title: 'WooCommerce Admin/components/Link',
component: Link,
decorators: [ ( storyFn, context ) => withConsole()( storyFn )( context ) ],
};
export const External = () => {
return (
<Link href="https://woocommerce.com" type="external">
<Link
href="https://woocommerce.com"
type="external"
onClick={ logLinkClick }
>
WooCommerce.com
</Link>
);
};
export const WCAdmin = () => {
return (
<Link
href="admin.php?page=wc-admin&path=%2Fanalytics%2Forders"
type="wc-admin"
onClick={ logLinkClick }
>
Analytics: Orders
</Link>
);
};
export const WPAdmin = () => {
return (
<Link
href="post-new.php?post_type=product"
type="wp-admin"
onClick={ logLinkClick }
>
New Product
</Link>
);
};

View File

@ -0,0 +1,124 @@
/**
* External dependencies
*/
import { fireEvent, render } from '@testing-library/react';
/**
* Internal dependencies
*/
import Link from '../index';
describe( 'Link', () => {
it( 'should render `external` links', () => {
const { container } = render(
<Link href="https://woocommerce.com" type="external">
WooCommerce.com
</Link>
);
expect( container.firstChild ).toMatchInlineSnapshot( `
<a
data-link-type="external"
href="https://woocommerce.com"
>
WooCommerce.com
</a>
` );
} );
it( 'should render `wp-admin` links', () => {
const { container } = render(
<Link href="post-new.php?post_type=product" type="wp-admin">
New Post
</Link>
);
expect( container.firstChild ).toMatchInlineSnapshot( `
<a
data-link-type="wp-admin"
href="post-new.php?post_type=product"
>
New Post
</a>
` );
} );
it( 'should render `wc-admin` links', () => {
const { container } = render(
<Link
href="admin.php?page=wc-admin&path=%2Fanalytics%2Forders"
type="wc-admin"
>
Analytics: Orders
</Link>
);
expect( container.firstChild ).toMatchInlineSnapshot( `
<a
data-link-type="wc-admin"
href="admin.php?page=wc-admin&path=%2Fanalytics%2Forders"
>
Analytics: Orders
</a>
` );
} );
it( 'should render links without a type as `wc-admin`', () => {
const { container } = render(
<Link href="admin.php?page=wc-admin&path=%2Fanalytics%2Forders">
Analytics: Orders
</Link>
);
expect( container.firstChild ).toMatchInlineSnapshot( `
<a
data-link-type="wc-admin"
href="admin.php?page=wc-admin&path=%2Fanalytics%2Forders"
>
Analytics: Orders
</a>
` );
} );
it( 'should allow custom props to be passed through', () => {
const { container } = render(
<Link
href="https://woocommerce.com"
type="external"
className="foo"
target="bar"
>
WooCommerce.com
</Link>
);
expect( container.firstChild ).toMatchInlineSnapshot( `
<a
class="foo"
data-link-type="external"
href="https://woocommerce.com"
target="bar"
>
WooCommerce.com
</a>
` );
} );
it( 'should support `onClick`', () => {
const clickHandler = jest.fn();
const { container } = render(
<Link
href="https://woocommerce.com"
type="external"
onClick={ clickHandler }
>
WooCommerce.com
</Link>
);
fireEvent.click( container.firstChild );
expect( clickHandler ).toHaveBeenCalled();
} );
} );

View File

@ -21,6 +21,16 @@ class List extends Component {
}
}
getItemLinkType( item ) {
const { href, linkType } = item;
if ( linkType ) {
return linkType;
}
return href ? 'external' : null;
}
render() {
const { className, items } = this.props;
const listClassName = classnames( 'woocommerce-list', className );
@ -34,6 +44,7 @@ class List extends Component {
className: itemClasses,
content,
href,
listItemTag,
onClick,
target,
title,
@ -57,8 +68,9 @@ class List extends Component {
onKeyDown: ( e ) =>
hasAction ? this.handleKeyDown( e, onClick ) : null,
target: href ? target : null,
type: href ? 'external' : null,
type: this.getItemLinkType( item ),
href,
'data-list-item-tag': listItemTag,
};
return (

View File

@ -2,6 +2,7 @@
* External dependencies
*/
import Gridicon from 'gridicons';
import { withConsole } from '@storybook/addon-console';
/**
* Internal dependencies
@ -9,9 +10,27 @@ import Gridicon from 'gridicons';
import List from '../';
import './style.scss';
function logItemClick( event ) {
const a = event.currentTarget;
const itemDescription = a.href
? `[${ a.textContent }](${ a.href }) ${ a.dataset.linkType }`
: `[${ a.textContent }]`;
const itemTag = a.dataset.listItemTag
? `'${ a.dataset.listItemTag }'`
: 'not set';
const logMessage = `[${ itemDescription } item clicked (tag: ${ itemTag })`;
// eslint-disable-next-line no-console
console.log( logMessage );
event.preventDefault();
return false;
}
export default {
title: 'WooCommerce Admin/components/List',
component: List,
decorators: [ ( storyFn, context ) => withConsole()( storyFn )( context ) ],
};
export const Default = () => {
@ -19,10 +38,12 @@ export const Default = () => {
{
title: 'WooCommerce.com',
href: 'https://woocommerce.com',
onClick: logItemClick,
},
{
title: 'WordPress.org',
href: 'https://wordpress.org',
onClick: logItemClick,
},
{
title: 'A list item with no action',
@ -30,9 +51,10 @@ export const Default = () => {
{
title: 'Click me!',
content: 'An alert will be triggered.',
onClick: () => {
onClick: ( event ) => {
// eslint-disable-next-line no-alert
window.alert( 'List item clicked' );
return logItemClick( event );
},
},
];
@ -47,12 +69,14 @@ export const BeforeAndAfter = () => {
after: <Gridicon icon="chevron-right" />,
title: 'WooCommerce.com',
href: 'https://woocommerce.com',
onClick: logItemClick,
},
{
before: <Gridicon icon="my-sites" />,
after: <Gridicon icon="chevron-right" />,
title: 'WordPress.org',
href: 'https://wordpress.org',
onClick: logItemClick,
},
{
before: <Gridicon icon="link-break" />,
@ -63,9 +87,10 @@ export const BeforeAndAfter = () => {
before: <Gridicon icon="notice" />,
title: 'Click me!',
content: 'An alert will be triggered.',
onClick: () => {
onClick: ( event ) => {
// eslint-disable-next-line no-alert
window.alert( 'List item clicked' );
return logItemClick( event );
},
},
];
@ -73,19 +98,23 @@ export const BeforeAndAfter = () => {
return <List items={ listItems } />;
};
export const CustomStyle = () => {
export const CustomStyleAndTags = () => {
const listItems = [
{
before: <Gridicon icon="cart" />,
after: <Gridicon icon="chevron-right" />,
title: 'WooCommerce.com',
href: 'https://woocommerce.com',
onClick: logItemClick,
listItemTag: 'woocommerce.com-link',
},
{
before: <Gridicon icon="my-sites" />,
after: <Gridicon icon="chevron-right" />,
title: 'WordPress.org',
href: 'https://wordpress.org',
onClick: logItemClick,
listItemTag: 'wordpress.org-link',
},
{
before: <Gridicon icon="link-break" />,
@ -95,10 +124,12 @@ export const CustomStyle = () => {
before: <Gridicon icon="notice" />,
title: 'Click me!',
content: 'An alert will be triggered.',
onClick: () => {
onClick: ( event ) => {
// eslint-disable-next-line no-alert
window.alert( 'List item clicked' );
return logItemClick( event );
},
listItemTag: 'click-me',
},
];

View File

@ -0,0 +1,141 @@
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
import List from '../index';
describe( 'List', () => {
it( 'should have aria roles for items', () => {
const clickHandler = jest.fn();
const listItems = [
{
title: 'WooCommerce.com',
href: 'https://woocommerce.com',
},
{
title: 'Click me!',
onClick: clickHandler,
},
];
render( <List items={ listItems } /> );
expect( screen.getAllByRole( 'menuitem' ) ).toHaveLength( 2 );
} );
it( 'should support `onClick` for items', () => {
const clickHandler = jest.fn();
const listItems = [
{
title: 'WooCommerce.com',
href: 'https://woocommerce.com',
},
{
title: 'Click me!',
onClick: clickHandler,
},
];
render( <List items={ listItems } /> );
userEvent.click(
screen.getByRole( 'menuitem', { name: 'Click me!' } )
);
expect( clickHandler ).toHaveBeenCalled();
} );
it( 'should set `data-link-type` on items', () => {
const listItems = [
{
title: 'Add products',
href: '/post-new.php?post_type=product',
linkType: 'wp-admin',
},
{
title: 'Market my store',
href: '/admin.php?page=wc-admin&path=%2Fmarketing',
linkType: 'wc-admin',
},
{
title: 'WooCommerce.com',
href: 'https://woocommerce.com',
linkType: 'external',
},
{
title: 'WordPress.org',
href: 'https://wordpress.org',
},
];
render( <List items={ listItems } /> );
expect(
screen.getByRole( 'menuitem', { name: 'Add products' } ).dataset
.linkType
).toBe( 'wp-admin' );
expect(
screen.getByRole( 'menuitem', { name: 'Market my store' } ).dataset
.linkType
).toBe( 'wc-admin' );
expect(
screen.getByRole( 'menuitem', { name: 'WooCommerce.com' } ).dataset
.linkType
).toBe( 'external' );
expect(
screen.getByRole( 'menuitem', { name: 'WordPress.org' } ).dataset
.linkType
).toBe( 'external' );
} );
it( 'should set `data-list-item-tag` on items', () => {
const listItems = [
{
title: 'Add products',
href: '/post-new.php?post_type=product',
linkType: 'wp-admin',
listItemTag: 'add-product',
},
{
title: 'Market my store',
href: '/admin.php?page=wc-admin&path=%2Fmarketing',
linkType: 'wc-admin',
listItemTag: 'marketing',
},
{
title: 'WooCommerce.com',
href: 'https://woocommerce.com',
linkType: 'external',
listItemTag: 'woocommerce.com-site',
},
{
title: 'WordPress.org',
href: 'https://wordpress.org',
},
];
render( <List items={ listItems } /> );
expect(
screen.getByRole( 'menuitem', { name: 'Add products' } ).dataset
.listItemTag
).toBe( 'add-product' );
expect(
screen.getByRole( 'menuitem', { name: 'Market my store' } ).dataset
.listItemTag
).toBe( 'marketing' );
expect(
screen.getByRole( 'menuitem', { name: 'WooCommerce.com' } ).dataset
.listItemTag
).toBe( 'woocommerce.com-site' );
expect(
screen.getByRole( 'menuitem', { name: 'WordPress.org' } ).dataset
.listItemTag
).toBeUndefined();
} );
} );