Skip to content

Commit

Permalink
Add eventType to onToggle and source for handleClose (#2422)
Browse files Browse the repository at this point in the history
* Pass DOM event and source to onToggle

* Add tests to DropDownSpec

- Add tests to ensure that event and source are passed to onToggle

* Update react-overlays package version

* Modify DropdownMenuSpec test

- Update the test to reflect changes made to RootCloseWrapper in react-overlays react-bootstrap/react-overlays#142

* WIP

Failing a test in DropdownMenuSpec. Committing to push for debugging

* Minor fixes
  • Loading branch information
joelseq authored and jquense committed Mar 5, 2017
1 parent 45f2b05 commit c87409a
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 22 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -124,7 +124,7 @@
"dom-helpers": "^3.2.0",
"invariant": "^2.2.1",
"keycode": "^2.1.2",
"react-overlays": "^0.6.10",
"react-overlays": "^0.6.11",
"react-prop-types": "^0.4.0",
"uncontrollable": "^4.0.1",
"warning": "^3.0.0"
Expand Down
30 changes: 17 additions & 13 deletions src/Dropdown.js
Expand Up @@ -71,10 +71,10 @@ const propTypes = {

/**
* A callback fired when the Dropdown wishes to change visibility. Called with the requested
* `open` value.
* `open` value, the DOM event, and the source that fired it: `'click'`,`'keydown'`,`'rootClose'`, or `'select'`.
*
* ```js
* function(Boolean isOpen) {}
* function(Boolean isOpen, Object event, { String source }) {}
* ```
* @controllable open
*/
Expand Down Expand Up @@ -156,12 +156,12 @@ class Dropdown extends React.Component {
}
}

handleClick() {
handleClick(event) {
if (this.props.disabled) {
return;
}

this.toggleOpen('click');
this.toggleOpen(event, { source: 'click' });
}

handleKeyDown(event) {
Expand All @@ -172,38 +172,38 @@ class Dropdown extends React.Component {
switch (event.keyCode) {
case keycode.codes.down:
if (!this.props.open) {
this.toggleOpen('keydown');
this.toggleOpen(event, { source: 'keydown' });
} else if (this.menu.focusNext) {
this.menu.focusNext();
}
event.preventDefault();
break;
case keycode.codes.esc:
case keycode.codes.tab:
this.handleClose(event);
this.handleClose(event, { source: 'keydown' });
break;
default:
}
}

toggleOpen(eventType) {
toggleOpen(event, eventDetails) {
let open = !this.props.open;

if (open) {
this.lastOpenEventType = eventType;
this.lastOpenEventType = eventDetails.source;
}

if (this.props.onToggle) {
this.props.onToggle(open);
this.props.onToggle(open, event, eventDetails);
}
}

handleClose() {
handleClose(event, eventDetails) {
if (!this.props.open) {
return;
}

this.toggleOpen(null);
this.toggleOpen(event, eventDetails);
}

focusNextOnOpen() {
Expand Down Expand Up @@ -274,10 +274,14 @@ class Dropdown extends React.Component {
labelledBy: id,
bsClass: prefix(props, 'menu'),
onClose: createChainedFunction(
child.props.onClose, onClose, this.handleClose,
child.props.onClose,
onClose,
this.handleClose,
),
onSelect: createChainedFunction(
child.props.onSelect, onSelect, this.handleClose,
child.props.onSelect,
onSelect,
(key, event) => this.handleClose(event, { source: 'select' }),
),
rootCloseEvent
});
Expand Down
20 changes: 13 additions & 7 deletions src/DropdownMenu.js
Expand Up @@ -4,7 +4,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import RootCloseWrapper from 'react-overlays/lib/RootCloseWrapper';

import { bsClass, getClassSet, prefix, splitBsProps }
import { bsClass, getClassSet, prefix, splitBsPropsAndOmit }
from './utils/bootstrapUtils';
import createChainedFunction from './utils/createChainedFunction';
import ValidComponentChildren from './utils/ValidComponentChildren';
Expand All @@ -29,9 +29,14 @@ class DropdownMenu extends React.Component {
constructor(props) {
super(props);

this.handleRootClose = this.handleRootClose.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}

handleRootClose(event) {
this.props.onClose(event, { source: 'rootClose' });
}

handleKeyDown(event) {
switch (event.keyCode) {
case keycode.codes.down:
Expand All @@ -44,7 +49,7 @@ class DropdownMenu extends React.Component {
break;
case keycode.codes.esc:
case keycode.codes.tab:
this.props.onClose(event);
this.props.onClose(event, { source: 'keydown' });
break;
default:
}
Expand Down Expand Up @@ -90,7 +95,6 @@ class DropdownMenu extends React.Component {
const {
open,
pullRight,
onClose,
labelledBy,
onSelect,
className,
Expand All @@ -99,7 +103,7 @@ class DropdownMenu extends React.Component {
...props
} = this.props;

const [bsProps, elementProps] = splitBsProps(props);
const [bsProps, elementProps] = splitBsPropsAndOmit(props, ['onClose']);

const classes = {
...getClassSet(bsProps),
Expand All @@ -109,7 +113,7 @@ class DropdownMenu extends React.Component {
return (
<RootCloseWrapper
disabled={!open}
onRootClose={onClose}
onRootClose={this.handleRootClose}
event={rootCloseEvent}
>
<ul
Expand All @@ -121,9 +125,11 @@ class DropdownMenu extends React.Component {
{ValidComponentChildren.map(children, child => (
React.cloneElement(child, {
onKeyDown: createChainedFunction(
child.props.onKeyDown, this.handleKeyDown
child.props.onKeyDown, this.handleKeyDown,
),
onSelect: createChainedFunction(
child.props.onSelect, onSelect,
),
onSelect: createChainedFunction(child.props.onSelect, onSelect),
})
))}
</ul>
Expand Down
2 changes: 1 addition & 1 deletion test/DropdownMenuSpec.js
Expand Up @@ -125,7 +125,7 @@ describe('<Dropdown.Menu>', () => {
button.click();

requestClose.should.have.been.calledOnce;
requestClose.getCall(0).args.length.should.equal(0);
requestClose.getCall(0).args.length.should.equal(2);
});

describe('Keyboard Navigation', () => {
Expand Down
101 changes: 101 additions & 0 deletions test/DropdownSpec.js
Expand Up @@ -526,6 +526,107 @@ describe('<Dropdown>', () => {
});
});

describe('DOM event and source passed to onToggle', () => {
let focusableContainer;

beforeEach(() => {
focusableContainer = document.createElement('div');
document.body.appendChild(focusableContainer);
});

afterEach(() => {
ReactDOM.unmountComponentAtNode(focusableContainer);
document.body.removeChild(focusableContainer);
});

it('passes open, event, and source correctly when opened with click', () => {
const spy = sinon.spy();
const instance = ReactTestUtils.renderIntoDocument(
<Dropdown id="test-id" onToggle={spy}>
{dropdownChildren}
</Dropdown>
);
const buttonNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'BUTTON');

expect(spy).to.not.have.been.called;

ReactTestUtils.Simulate.click(buttonNode);

expect(spy).to.have.been.calledOnce;
expect(spy.getCall(0).args.length).to.equal(3);
expect(spy.getCall(0).args[0]).to.equal(true);
expect(spy.getCall(0).args[1]).to.be.an('object');
assert.deepEqual(spy.getCall(0).args[2], { source: 'click' });
});

it('passes open, event, and source correctly when closed with click', () => {
const spy = sinon.spy();
const instance = ReactTestUtils.renderIntoDocument(
<Dropdown id="test-id" onToggle={spy}>
{dropdownChildren}
</Dropdown>
);
const buttonNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'BUTTON');

expect(spy).to.not.have.been.called;
ReactTestUtils.Simulate.click(buttonNode);
expect(spy).to.have.been.calledOnce;
ReactTestUtils.Simulate.click(buttonNode);

expect(spy).to.have.been.calledTwice;
expect(spy.getCall(1).args.length).to.equal(3);
expect(spy.getCall(1).args[0]).to.equal(false);
expect(spy.getCall(1).args[1]).to.be.an('object');
assert.deepEqual(spy.getCall(1).args[2], { source: 'click' });
});

it('passes open, event, and source correctly when child selected', () => {
const spy = sinon.spy();
const instance = ReactTestUtils.renderIntoDocument(
<Dropdown id="test-id" onToggle={spy}>
<Dropdown.Toggle key="toggle">
Child Title
</Dropdown.Toggle>
<Dropdown.Menu key="menu">
<MenuItem eventKey={1}>Item 1</MenuItem>
</Dropdown.Menu>
</Dropdown>
);
const buttonNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'BUTTON');
const childNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'A');

expect(spy).to.not.have.been.called;
ReactTestUtils.Simulate.click(buttonNode);
expect(spy).to.have.been.calledOnce;

ReactTestUtils.Simulate.click(childNode);

expect(spy).to.have.been.calledTwice;
expect(spy.getCall(1).args.length).to.equal(3);
expect(spy.getCall(1).args[0]).to.equal(false);
expect(spy.getCall(1).args[1]).to.be.an('object');
assert.deepEqual(spy.getCall(1).args[2], { source: 'select' });
});

it('passes open, event, and source correctly when opened with keydown', () => {
const spy = sinon.spy();
const instance = ReactTestUtils.renderIntoDocument(
<Dropdown id="test-id" onToggle={spy}>
{dropdownChildren}
</Dropdown>
);
const buttonNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'BUTTON');

ReactTestUtils.Simulate.keyDown(buttonNode, { key: 'Down Arrow', keyCode: 40, which: 40 });

expect(spy).to.have.been.calledOnce;
expect(spy.getCall(0).args.length).to.equal(3);
expect(spy.getCall(0).args[0]).to.equal(true);
expect(spy.getCall(0).args[1]).to.be.an('object');
assert.deepEqual(spy.getCall(0).args[2], { source: 'keydown' });
});
});

it('should derive bsClass from parent', () => {
const instance = ReactTestUtils.renderIntoDocument(
<Dropdown bsClass="my-dropdown" id="test-id">
Expand Down

0 comments on commit c87409a

Please sign in to comment.