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

feat: add experimental Popover to ToolbarButton #1469

Closed
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
08ca979
feat(cloud-cognitive): add `popover` example
Nov 2, 2021
f2ba1e8
Revert "feat(cloud-cognitive): add `popover` example"
Nov 2, 2021
394226b
Revert "Revert "feat(cloud-cognitive): add `popover` example""
Nov 2, 2021
80b5cf1
feat(cloud-cognitive): add `popover` example
Nov 2, 2021
389d128
Merge branch 'main' into toolbar-popover
Nov 9, 2021
8f3e428
build: promote Carbon transform ignore pattern
Nov 10, 2021
5e96b47
refactor(cloud-cognitive): update `ToolbarButton` story and array helper
Nov 10, 2021
0d1a0f2
refactor(cloud-cognitive): use `renderCaret` `ToolbarButton` prop
Nov 10, 2021
8781a6c
test(cloud-cognitive): progress `ToolbarButton` tests
Nov 10, 2021
72d73bb
chore: merge `main` into `toolbar-popover`
Nov 12, 2021
0173d88
test(cloud-cognitive): progress `ToolbarButton` tests
Nov 12, 2021
787850d
build: remove third-party package for composing refs
Nov 22, 2021
7c9084f
build: add `aria-haspopup` to spelling definition
Nov 22, 2021
17b0b99
test: refactor `Toolbar` test
Nov 22, 2021
994ed4f
Merge branch 'main' into toolbar-popover
SimonFinney Nov 22, 2021
8f3c0a8
Merge branch 'main' into toolbar-popover
dcwarwick Nov 22, 2021
38805eb
chore: revert `transformIgnorePatterns`
Nov 23, 2021
55fc7b0
build: add `@carbon/styles`
Nov 23, 2021
bd2053e
refactor: use Carbon entry point
Nov 23, 2021
9131117
chore: add `@carbon/styles` TODO
Nov 23, 2021
2a717bc
build: clear Yarn cache
Nov 23, 2021
fb2db1c
Merge branch 'main' into toolbar-popover
SimonFinney Nov 23, 2021
3eea32f
chore: merge `main`
Nov 29, 2021
dd5ae3f
refactor: prevent `@use` compilation error
Nov 29, 2021
48086df
Merge branch 'toolbar-popover' of github.com:SimonFinney/ibm-cloud-co…
Nov 29, 2021
68152be
refactor: simplify `ToolbarButton` test utility
Nov 29, 2021
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
4 changes: 2 additions & 2 deletions config/jest-config-ibm-cloud-cognitive/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright IBM Corp. 2020, 2020
* Copyright IBM Corp. 2020, 2021
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
Expand Down Expand Up @@ -72,7 +72,7 @@ module.exports = {
'templates',
'/umd/',
],
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'],
transformIgnorePatterns: ['node_modules/(?!carbon-components-react)'],
watchPathIgnorePatterns: [
'/cjs/',
'/dist/',
Expand Down
7 changes: 2 additions & 5 deletions cspell.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
{
"version": "0.1",
"language": "en",
"dictionaries": [
"contributors",
"html",
"packages"
],
"dictionaries": ["contributors", "html", "packages"],
"dictionaryDefinitions": [
{
"name": "contributors",
Expand Down Expand Up @@ -69,6 +65,7 @@
"apikey",
"checkmark",
"data-testid",
"haspopup",
"homescreen",
"loglevel",
"noreply",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
SettingsAdjust16,
Table16,
TextAlignCenter16,
TextAlignLeft16,
TextAlignRight16,
TextCreation16,
ZoomIn16,
ZoomOut16,
Expand Down Expand Up @@ -57,11 +59,22 @@ export default {
},
};

function middle(array) {
return array[Math.floor(array.length / 2)];
}

function _Toolbar(args) {
const buttons = [
{ iconDescription: 'Text align left', renderIcon: TextAlignLeft16 },
{ iconDescription: 'Text align center', renderIcon: TextAlignCenter16 },
{ iconDescription: 'Text align right', renderIcon: TextAlignRight16 },
];

const dropdownItems = ['11', '12', '14', '16', '18'];

const [selectedButton, setSelectedButton] = useState(middle(buttons));
const [selectedDropdownItem, setSelectedDropdownItem] = useState(
dropdownItems[(dropdownItems.length / 2) | 0]
middle(dropdownItems)
);

return (
Expand Down Expand Up @@ -104,9 +117,16 @@ function _Toolbar(args) {

<ToolbarGroup>
<ToolbarButton
iconDescription="Text align center"
renderIcon={TextAlignCenter16}
caret
{...selectedButton}
renderCaret={() =>
buttons.map((props, index) => (
<ToolbarButton
key={`${ToolbarButton.displayName}--${index}`}
{...props}
onClick={() => setSelectedButton(props)}
/>
))
}
/>
</ToolbarGroup>

Expand Down
188 changes: 152 additions & 36 deletions packages/cloud-cognitive/src/components/Toolbar/Toolbar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,69 @@
* LICENSE file in the root directory of this source tree.
*/

import { render as r, screen } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { settings } from 'carbon-components';
import React, { createRef } from 'react';

import { Toolbar, ToolbarButton, ToolbarGroup } from '../..';
import uuidv4 from '../../global/js/utils/uuidv4';

import { blockClass, componentName } from './Toolbar';
import { blockClass as toolbarButtonClass } from './ToolbarButton';

const { getByTestId, getByText } = screen;
const { getByTestId, getByText, queryByText } = screen;
const { click } = userEvent;

const dataTestId = 'dataTestId';
function instance(prop) {
return `${uuidv4()}--${prop}`;
}

function _render(props) {
const Component = this;
function toBeAccessible(label, node, displayName) {
it(label, async () => {
const { container } = render(node);

return r(
<Component data-testid={dataTestId} {...props}>
{Component.displayName}
</Component>
);
await expect(container).toBeAccessible(`${displayName} — ${label}`);
await expect(container).toHaveNoAxeViolations();
});
}

function test(Component) {
const render = _render.bind(Component);
const { displayName } = Component;
const children = instance('children');
const dataTestId = instance('dataTestId');

it('has no accessibility violations', async () => {
const { container } = render();
const props = { children };

await expect(container).toBeAccessible(displayName);
await expect(container).toHaveNoAxeViolations();
});
function test(Component) {
toBeAccessible(
'has no accessibility violations',
<Component {...props} />,
Component.displayName
);

it('renders children', () => {
render();
render(<Component {...props} />);

getByText(displayName);
getByText(children);
});

it('adds a class to the containing node', () => {
const className = 'class-name';
render({ className });
const className = instance('class-name');
render(
<Component {...props} className={className} data-testid={dataTestId} />
);

expect(getByTestId(dataTestId)).toHaveClass(className);
});

it('adds additional props to the containing node', () => {
render();
render(<Component {...props} data-testid={dataTestId} />);

getByTestId(dataTestId);
});

it('forwards a reference to the appropriate DOM node', () => {
const ref = createRef();
render({ ref });
render(<Component {...props} ref={ref} data-testid={dataTestId} />);

expect(getByTestId(dataTestId)).toEqual(ref.current);
});
Expand All @@ -67,33 +76,140 @@ function test(Component) {
describe(componentName, () => {
test(Toolbar);

toBeAccessible(
'has no accessibility violations for the vertical variant',
<Toolbar {...props} vertical />,
componentName
);

it('renders the vertical variant', () => {
_render.bind(Toolbar)({ vertical: true });
const { rerender } = render(
<Toolbar {...props} data-testid={dataTestId} />
);

const className = `${blockClass}--vertical`;
const toolbar = getByTestId(dataTestId);
expect(toolbar).not.toHaveClass(className);

rerender(<Toolbar {...props} data-testid={dataTestId} vertical />);

expect(toolbar).toHaveAttribute('aria-orientation', 'vertical');
expect(toolbar).toHaveClass(`${blockClass}--vertical`);
expect(toolbar).toHaveClass(className);
});
});

describe(ToolbarButton.displayName, () => {
test(ToolbarButton);

const { fn } = jest;

const open = { open: true };
const renderCaret = instance('renderCaret');

function CaretToolbarButton(rest) {
return (
<ToolbarButton
{...props}
{...{
renderCaret: () => renderCaret,
...rest,
}}
/>
);
}

it('calls `onClick` when clicked', () => {
const onClick = fn();
render(
<ToolbarButton {...props} data-testid={dataTestId} onClick={onClick} />
);

expect(onClick).not.toBeCalled();

click(getByTestId(dataTestId));
expect(onClick).toBeCalledTimes(1);
});

toBeAccessible(
'has no accessibility violations for the caret variant',
<CaretToolbarButton />,
ToolbarButton.displayName
);

it('renders the caret variant', () => {
_render.bind(ToolbarButton)({ caret: true });
const { rerender } = render(<ToolbarButton data-testid={dataTestId} />);

const className = `${toolbarButtonClass}--caret`;
expect(getByTestId(dataTestId)).not.toHaveClass(className);

rerender(<CaretToolbarButton data-testid={dataTestId} />);
expect(getByTestId(dataTestId)).toHaveClass(className);
});

toBeAccessible(
'has no accessibility violations for the popover',
<CaretToolbarButton popover={open} />,
ToolbarButton.displayName
);

it('renders the popover', () => {
const { rerender } = render(<ToolbarButton data-testid={dataTestId} />);

rerender(<CaretToolbarButton data-testid={dataTestId} />);

expect(queryByText(renderCaret)).not.toBeInTheDocument();

click(getByTestId(dataTestId));
getByText(renderCaret);
});

it('adds additional props to the popover', () => {
render(
<CaretToolbarButton popover={{ ...open, 'data-testid': dataTestId }} />
);

getByTestId(dataTestId);
});

it('calls `onClose` when the popover is closed', () => {
const onClose = fn();
render(<CaretToolbarButton data-testid={dataTestId} onClose={onClose} />);

expect(getByTestId(dataTestId)).toHaveClass(`${blockClass}__button--caret`);
click(getByTestId(dataTestId));
expect(onClose).not.toBeCalled();

click(getByTestId(dataTestId));
expect(onClose).toBeCalledTimes(1);
});

it('calls `onOpen` when the popover is opened', () => {
const onOpen = fn();
render(<CaretToolbarButton data-testid={dataTestId} onOpen={onOpen} />);

expect(onOpen).not.toBeCalled();

click(getByTestId(dataTestId));
expect(onOpen).toBeCalledTimes(1);
});

it('closes the popover when clicked outside', () => {
render(<CaretToolbarButton popover={open} />);

click(document.body);
expect(queryByText(renderCaret)).not.toBeInTheDocument();
});

it("renders the 'right' tooltip position for the vertical variant by default", () => {
expect(
r(
<Toolbar vertical>
<ToolbarButton data-testid={dataTestId} />
</Toolbar>
).getByTestId(dataTestId)
).toHaveClass(`${settings.prefix}--btn--icon-only--right`);
const { rerender } = render(<ToolbarButton data-testid={dataTestId} />);

const className = `${settings.prefix}--btn--icon-only--right`;
expect(getByTestId(dataTestId)).not.toHaveClass(className);

rerender(
<Toolbar vertical>
<ToolbarButton data-testid={dataTestId} />
</Toolbar>
);
expect(getByTestId(dataTestId)).toHaveClass(className);
});
});

Expand Down
Loading