Skip to content

Commit

Permalink
[SpeedDialAction] Convert to hook (#16386)
Browse files Browse the repository at this point in the history
* Converted SpeedDialAction to hook

* Updated test in SpeedDialAction, changed simulate to props.onClick

* ran prettier

* removed withForwardedRef in favor or React.forwardRef

* Reverted SpeedDialAction test

* remove keycode dependency

* fix the tests
  • Loading branch information
adeelibr authored and oliviertassinari committed Jul 7, 2019
1 parent 673caa4 commit 7d826a9
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 174 deletions.
7 changes: 3 additions & 4 deletions packages/material-ui-lab/src/SpeedDial/SpeedDial.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import keycode from 'keycode';
import warning from 'warning';
import { duration, withStyles } from '@material-ui/core/styles';
import Zoom from '@material-ui/core/Zoom';
Expand Down Expand Up @@ -152,11 +151,11 @@ const SpeedDial = React.forwardRef(function SpeedDial(props, ref) {
};

const handleKeyboardNavigation = event => {
const key = keycode(event);
const key = event.key.replace('Arrow', '').toLowerCase();
const { current: nextItemArrowKeyCurrent = key } = nextItemArrowKey;

if (key === 'esc') {
closeActions(event, key);
if (event.key === 'Escape') {
closeActions(event, 'esc');
} else if (utils.sameOrientation(key, direction)) {
event.preventDefault();

Expand Down
171 changes: 117 additions & 54 deletions packages/material-ui-lab/src/SpeedDial/SpeedDial.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { codes as keycodes } from 'keycode';
import React from 'react';
import { assert } from 'chai';
import { spy } from 'sinon';
Expand All @@ -19,8 +18,9 @@ describe('<SpeedDial />', () => {

const icon = <Icon>font_icon</Icon>;
const FakeAction = () => <div />;
const defaultProps = {
const props = {
open: true,
icon,
ariaLabel: 'mySpeedDial',
};

Expand All @@ -33,7 +33,7 @@ describe('<SpeedDial />', () => {
// StrictModeViolation: uses ButtonBase
mount = createMount({ strict: false });
classes = getClasses(
<SpeedDial {...defaultProps} icon={icon}>
<SpeedDial {...props} icon={icon}>
<div />
</SpeedDial>,
);
Expand All @@ -45,7 +45,7 @@ describe('<SpeedDial />', () => {

it('should render with a minimal setup', () => {
const wrapper = mount(
<SpeedDial {...defaultProps} icon={icon}>
<SpeedDial {...props} icon={icon}>
<SpeedDialAction icon={<Icon>save_icon</Icon>} tooltipTitle="Save" />
</SpeedDial>,
);
Expand All @@ -54,7 +54,7 @@ describe('<SpeedDial />', () => {

it('should render a Fade transition', () => {
const wrapper = mount(
<SpeedDial {...defaultProps} icon={icon}>
<SpeedDial {...props} icon={icon}>
<FakeAction />
</SpeedDial>,
);
Expand All @@ -63,7 +63,7 @@ describe('<SpeedDial />', () => {

it('should render a Fab', () => {
const wrapper = mount(
<SpeedDial {...defaultProps} icon={icon}>
<SpeedDial {...props} icon={icon}>
<FakeAction />
</SpeedDial>,
);
Expand All @@ -73,7 +73,7 @@ describe('<SpeedDial />', () => {

it('should render with a null child', () => {
const wrapper = mount(
<SpeedDial {...defaultProps} icon={icon}>
<SpeedDial {...props} icon={icon}>
<SpeedDialAction icon={icon} tooltipTitle="One" />
{null}
<SpeedDialAction icon={icon} tooltipTitle="Three" />
Expand All @@ -84,7 +84,7 @@ describe('<SpeedDial />', () => {

it('should render with the root class', () => {
const wrapper = mount(
<SpeedDial {...defaultProps} icon={icon}>
<SpeedDial {...props} icon={icon}>
<FakeAction />
</SpeedDial>,
);
Expand All @@ -93,7 +93,7 @@ describe('<SpeedDial />', () => {

it('should render with the user and root classes', () => {
const wrapper = mount(
<SpeedDial {...defaultProps} className="mySpeedDialClass" icon={icon}>
<SpeedDial {...props} className="mySpeedDialClass" icon={icon}>
<FakeAction />
</SpeedDial>,
);
Expand All @@ -108,7 +108,7 @@ describe('<SpeedDial />', () => {

it('should render the actions with the actions class', () => {
const wrapper = mount(
<SpeedDial {...defaultProps} className="mySpeedDial" icon={icon}>
<SpeedDial {...props} className="mySpeedDial" icon={icon}>
<SpeedDialAction icon={icon} tooltipTitle="SpeedDialAction" />
</SpeedDial>,
);
Expand All @@ -119,7 +119,7 @@ describe('<SpeedDial />', () => {

it('should render the actions with the actions and actionsClosed classes', () => {
const wrapper = mount(
<SpeedDial {...defaultProps} open={false} className="mySpeedDial" icon={icon}>
<SpeedDial {...props} open={false} className="mySpeedDial" icon={icon}>
<SpeedDialAction icon={icon} tooltipTitle="SpeedDialAction" />
</SpeedDial>,
);
Expand All @@ -131,7 +131,7 @@ describe('<SpeedDial />', () => {
it('should pass the open prop to its children', () => {
const actionClasses = { buttonClosed: 'is-closed' };
const wrapper = mount(
<SpeedDial {...defaultProps} icon={icon}>
<SpeedDial {...props} icon={icon}>
<SpeedDialAction classes={actionClasses} icon={icon} tooltipTitle="SpeedDialAction1" />
<SpeedDialAction classes={actionClasses} icon={icon} tooltipTitle="SpeedDialAction2" />
</SpeedDial>,
Expand All @@ -144,7 +144,7 @@ describe('<SpeedDial />', () => {
it('should be set as the onClick prop of the Fab', () => {
const onClick = spy();
const wrapper = mount(
<SpeedDial {...defaultProps} icon={icon} onClick={onClick}>
<SpeedDial {...props} icon={icon} onClick={onClick}>
<FakeAction />
</SpeedDial>,
);
Expand All @@ -161,7 +161,7 @@ describe('<SpeedDial />', () => {
const onClick = spy();

const wrapper = mount(
<SpeedDial {...defaultProps} icon={icon} onClick={onClick}>
<SpeedDial {...props} icon={icon} onClick={onClick}>
<FakeAction />
</SpeedDial>,
);
Expand All @@ -179,13 +179,16 @@ describe('<SpeedDial />', () => {
it('should be called when a key is pressed', () => {
const handleKeyDown = spy();
const wrapper = mount(
<SpeedDial {...defaultProps} icon={icon} onKeyDown={handleKeyDown}>
<SpeedDial {...props} icon={icon} onKeyDown={handleKeyDown}>
<FakeAction />
</SpeedDial>,
);
const buttonWrapper = wrapper.find('[aria-expanded]').first();
const eventMock = 'something-to-match';
buttonWrapper.simulate('keyDown', { eventMock });
buttonWrapper.simulate('keyDown', {
key: ' ',
eventMock,
});
assert.strictEqual(handleKeyDown.callCount, 1);
assert.strictEqual(handleKeyDown.calledWithMatch({ eventMock }), true);
});
Expand All @@ -195,7 +198,7 @@ describe('<SpeedDial />', () => {
const testDirection = direction => {
const className = `direction${direction}`;
const wrapper = mount(
<SpeedDial {...defaultProps} direction={direction.toLowerCase()} icon={icon}>
<SpeedDial {...props} direction={direction.toLowerCase()} icon={icon}>
<SpeedDialAction icon={icon} tooltipTitle="action1" />
<SpeedDialAction icon={icon} tooltipTitle="action2" />
</SpeedDial>,
Expand Down Expand Up @@ -229,7 +232,7 @@ describe('<SpeedDial />', () => {

wrapper = mount(
<SpeedDial
{...defaultProps}
{...props}
ButtonProps={{
ref: ref => {
dialButtonRef = ref;
Expand All @@ -248,6 +251,7 @@ describe('<SpeedDial />', () => {
},
}}
icon={icon}
data-test={i}
tooltipTitle={`action${i}`}
/>
))}
Expand All @@ -268,7 +272,10 @@ describe('<SpeedDial />', () => {
if (actionIndex === -1) {
return getDialButton();
}
return wrapper.find(SpeedDialAction).at(actionIndex);
return wrapper
.find(SpeedDialAction)
.at(actionIndex)
.find(Fab);
};
/**
* @returns true if the button of the nth action is focused
Expand Down Expand Up @@ -299,39 +306,39 @@ describe('<SpeedDial />', () => {
describe('first item selection', () => {
const createShouldAssertFirst = assertFn => (dialDirection, arrowKey) => {
resetDialToOpen(dialDirection);
getDialButton().simulate('keydown', { keyCode: keycodes[arrowKey] });
getDialButton().simulate('keydown', { key: arrowKey });
assertFn(isActionFocused(0));
};

const shouldFocusFirst = createShouldAssertFirst(assert.isTrue);
const shouldNotFocusFirst = createShouldAssertFirst(assert.isFalse);

it('considers arrow keys with the same orientation', () => {
shouldFocusFirst('up', 'up');
shouldFocusFirst('up', 'down');
shouldFocusFirst('up', 'ArrowUp');
shouldFocusFirst('up', 'ArrowDown');

shouldFocusFirst('down', 'up');
shouldFocusFirst('down', 'down');
shouldFocusFirst('down', 'ArrowUp');
shouldFocusFirst('down', 'ArrowDown');

shouldFocusFirst('right', 'right');
shouldFocusFirst('right', 'left');
shouldFocusFirst('right', 'ArrowRight');
shouldFocusFirst('right', 'ArrowLeft');

shouldFocusFirst('left', 'right');
shouldFocusFirst('left', 'left');
shouldFocusFirst('left', 'ArrowRight');
shouldFocusFirst('left', 'ArrowLeft');
});

it('ignores arrow keys orthogonal to the direction', () => {
shouldNotFocusFirst('up', 'left');
shouldNotFocusFirst('up', 'right');
shouldNotFocusFirst('up', 'ArrowLeft');
shouldNotFocusFirst('up', 'ArrowRight');

shouldNotFocusFirst('down', 'left');
shouldNotFocusFirst('down', 'right');
shouldNotFocusFirst('down', 'ArrowLeft');
shouldNotFocusFirst('down', 'ArrowRight');

shouldNotFocusFirst('right', 'up');
shouldNotFocusFirst('right', 'up');
shouldNotFocusFirst('right', 'ArrowUp');
shouldNotFocusFirst('right', 'ArrowUp');

shouldNotFocusFirst('left', 'down');
shouldNotFocusFirst('left', 'down');
shouldNotFocusFirst('left', 'ArrowDown');
shouldNotFocusFirst('left', 'ArrowDown');
});
});

Expand All @@ -349,7 +356,7 @@ describe('<SpeedDial />', () => {
) => {
resetDialToOpen(dialDirection);

getDialButton().simulate('keydown', { keyCode: keycodes[firstKey] });
getDialButton().simulate('keydown', { key: firstKey });
assert.strictEqual(
isActionFocused(firstFocusedAction),
true,
Expand All @@ -362,7 +369,7 @@ describe('<SpeedDial />', () => {
const combinationUntilNot = [firstKey, ...combination.slice(0, i + 1)];

getActionButton(previousFocusedAction).simulate('keydown', {
keyCode: keycodes[arrowKey],
key: arrowKey,
});
assert.strictEqual(
isActionFocused(expectedFocusedAction),
Expand All @@ -381,31 +388,87 @@ describe('<SpeedDial />', () => {
};

it('considers the first arrow key press as forward navigation', async () => {
await testCombination('up', ['up', 'up', 'up', 'down'], [0, 1, 2, 1]);
await testCombination('up', ['down', 'down', 'down', 'up'], [0, 1, 2, 1]);
await testCombination('up', ['ArrowUp', 'ArrowUp', 'ArrowUp', 'ArrowDown'], [0, 1, 2, 1]);
await testCombination(
'up',
['ArrowDown', 'ArrowDown', 'ArrowDown', 'ArrowUp'],
[0, 1, 2, 1],
);

await testCombination('right', ['right', 'right', 'right', 'left'], [0, 1, 2, 1]);
await testCombination('right', ['left', 'left', 'left', 'right'], [0, 1, 2, 1]);
await testCombination(
'right',
['ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowLeft'],
[0, 1, 2, 1],
);
await testCombination(
'right',
['ArrowLeft', 'ArrowLeft', 'ArrowLeft', 'ArrowRight'],
[0, 1, 2, 1],
);

await testCombination('down', ['down', 'down', 'down', 'up'], [0, 1, 2, 1]);
await testCombination('down', ['up', 'up', 'up', 'down'], [0, 1, 2, 1]);
await testCombination(
'down',
['ArrowDown', 'ArrowDown', 'ArrowDown', 'ArrowUp'],
[0, 1, 2, 1],
);
await testCombination('down', ['ArrowUp', 'ArrowUp', 'ArrowUp', 'ArrowDown'], [0, 1, 2, 1]);

await testCombination('left', ['left', 'left', 'left', 'right'], [0, 1, 2, 1]);
await testCombination('left', ['right', 'right', 'right', 'left'], [0, 1, 2, 1]);
await testCombination(
'left',
['ArrowLeft', 'ArrowLeft', 'ArrowLeft', 'ArrowRight'],
[0, 1, 2, 1],
);
await testCombination(
'left',
['ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowLeft'],
[0, 1, 2, 1],
);
});

it('ignores array keys orthogonal to the direction', async () => {
await testCombination('up', ['up', 'left', 'right', 'up'], [0, 0, 0, 1]);
await testCombination('right', ['right', 'up', 'down', 'right'], [0, 0, 0, 1]);
await testCombination('down', ['down', 'left', 'right', 'down'], [0, 0, 0, 1]);
await testCombination('left', ['left', 'up', 'down', 'left'], [0, 0, 0, 1]);
await testCombination(
'up',
['ArrowUp', 'ArrowLeft', 'ArrowRight', 'ArrowUp'],
[0, 0, 0, 1],
);
await testCombination(
'right',
['ArrowRight', 'ArrowUp', 'ArrowDown', 'ArrowRight'],
[0, 0, 0, 1],
);
await testCombination(
'down',
['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowDown'],
[0, 0, 0, 1],
);
await testCombination(
'left',
['ArrowLeft', 'ArrowUp', 'ArrowDown', 'ArrowLeft'],
[0, 0, 0, 1],
);
});

it('does not wrap around', async () => {
await testCombination('up', ['up', 'down', 'down', 'up'], [0, -1, -1, 0]);
await testCombination('right', ['right', 'left', 'left', 'right'], [0, -1, -1, 0]);
await testCombination('down', ['down', 'up', 'up', 'down'], [0, -1, -1, 0]);
await testCombination('left', ['left', 'right', 'right', 'left'], [0, -1, -1, 0]);
await testCombination(
'up',
['ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowUp'],
[0, -1, -1, 0],
);
await testCombination(
'right',
['ArrowRight', 'ArrowLeft', 'ArrowLeft', 'ArrowRight'],
[0, -1, -1, 0],
);
await testCombination(
'down',
['ArrowDown', 'ArrowUp', 'ArrowUp', 'ArrowDown'],
[0, -1, -1, 0],
);
await testCombination(
'left',
['ArrowLeft', 'ArrowRight', 'ArrowRight', 'ArrowLeft'],
[0, -1, -1, 0],
);
});
});
});
Expand Down
Loading

0 comments on commit 7d826a9

Please sign in to comment.