Skip to content

Commit 5d6804b

Browse files
RohanDamanibkrem
authored andcommitted
Implement onMouseOver feature (#50)
Finish implementation Add tests Add JSDoc Add docs
1 parent 6939773 commit 5d6804b

File tree

6 files changed

+98
-28
lines changed

6 files changed

+98
-28
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ module.exports = {
5252
"jsx-a11y/heading-has-content": 0,
5353
"jsx-a11y/href-no-hash": 2,
5454
"jsx-a11y/label-has-for": 2,
55-
"jsx-a11y/mouse-events-have-key-events": 2,
55+
"jsx-a11y/mouse-events-have-key-events": 0,
5656
"jsx-a11y/role-has-required-aria-props": 2,
5757
"jsx-a11y/role-supports-aria-props": 2,
5858
"max-len": 0,

README.md

Lines changed: 25 additions & 19 deletions
Large diffs are not rendered by default.

src/Node/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default class Node extends React.Component {
2020
};
2121

2222
this.handleClick = this.handleClick.bind(this);
23+
this.handleOnMouseOver = this.handleOnMouseOver.bind(this);
2324
}
2425

2526
componentDidMount() {
@@ -75,6 +76,10 @@ export default class Node extends React.Component {
7576
this.props.onClick(this.props.nodeData.id);
7677
}
7778

79+
handleOnMouseOver() {
80+
this.props.onMouseOver(this.props.nodeData.id);
81+
}
82+
7883
componentWillLeave(done) {
7984
const { nodeData: { parent }, orientation, transitionDuration } = this.props;
8085
const originX = parent ? parent.x : 0;
@@ -97,6 +102,8 @@ export default class Node extends React.Component {
97102
className={nodeData._children ? 'nodeBase' : 'leafNodeBase'}
98103
transform={this.state.transform}
99104
onClick={this.handleClick}
105+
onMouseOver={this.handleOnMouseOver}
106+
onFocus={this.handleOnMouseOver}
100107
>
101108
{/* TODO: DEPRECATE <circle /> */}
102109
{this.props.circleRadius ? (
@@ -142,6 +149,7 @@ Node.defaultProps = {
142149
textAnchor: 'start',
143150
attributes: undefined,
144151
circleRadius: undefined,
152+
onMouseOver: undefined,
145153
styles: {
146154
node: {
147155
circle: {},
@@ -162,6 +170,7 @@ Node.propTypes = {
162170
orientation: PropTypes.oneOf(['horizontal', 'vertical']).isRequired,
163171
transitionDuration: PropTypes.number.isRequired,
164172
onClick: PropTypes.func.isRequired,
173+
onMouseOver: PropTypes.func.isRequired,
165174
name: PropTypes.string.isRequired,
166175
attributes: PropTypes.object,
167176
textLayout: PropTypes.object.isRequired,

src/Node/tests/index.test.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('<Node />', () => {
3232
orientation: 'horizontal',
3333
transitionDuration: 500,
3434
onClick: () => {},
35+
onMouseOver: () => {},
3536
textLayout: {
3637
textAnchor: 'start',
3738
x: 10,
@@ -127,20 +128,24 @@ describe('<Node />', () => {
127128
expect(verticalComponent.find('g').prop('transform')).toBe(verticalTransform);
128129
});
129130

130-
it('should take an `onClick` prop', () => {
131-
const renderedComponent = shallow(<Node {...mockProps} onClick={() => {}} />);
132-
133-
expect(renderedComponent.prop('onClick')).toBeDefined();
134-
});
135-
136-
it('handles click events and passes its nodeId to onClick handler', () => {
131+
it('handles onClick events and passes its nodeId to onClick handler', () => {
137132
const onClickSpy = jest.fn();
138133
const renderedComponent = shallow(<Node {...mockProps} onClick={onClickSpy} />);
139134

140135
renderedComponent.simulate('click');
136+
expect(onClickSpy).toHaveBeenCalledTimes(1);
141137
expect(onClickSpy).toHaveBeenCalledWith(nodeData.id);
142138
});
143139

140+
it('handles onMouseOver events and passes its nodeId to onMouseOver handler', () => {
141+
const onMouseOverSpy = jest.fn();
142+
const renderedComponent = shallow(<Node {...mockProps} onMouseOver={onMouseOverSpy} />);
143+
144+
renderedComponent.simulate('mouseover');
145+
expect(onMouseOverSpy).toHaveBeenCalledTimes(1);
146+
expect(onMouseOverSpy).toHaveBeenCalledWith(nodeData.id);
147+
});
148+
144149
it('maps each `props.attributes` to a <tspan> element', () => {
145150
const fixture = { keyA: 'valA', keyB: 'valB' };
146151
const renderedComponent = shallow(<Node {...mockProps} attributes={fixture} />);

src/Tree/index.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default class Tree extends React.Component {
2121
this.collapseNode = this.collapseNode.bind(this);
2222
this.handleNodeToggle = this.handleNodeToggle.bind(this);
2323
this.handleOnClickCb = this.handleOnClickCb.bind(this);
24+
this.handleOnMouseOverCb = this.handleOnMouseOverCb.bind(this);
2425
}
2526

2627
componentDidMount() {
@@ -203,6 +204,23 @@ export default class Tree extends React.Component {
203204
}
204205
}
205206

207+
/**
208+
* handleOnMouseOverCb - Handles the user-defined `onMouseOver` function
209+
*
210+
* @param {string} nodeId
211+
*
212+
* @return {void}
213+
*/
214+
handleOnMouseOverCb(nodeId) {
215+
const { onMouseOver } = this.props;
216+
if (onMouseOver && typeof onMouseOver === 'function') {
217+
const data = clone(this.state.data);
218+
const matches = this.findNodesById(nodeId, data, []);
219+
const targetNode = matches[0];
220+
onMouseOver(clone(targetNode));
221+
}
222+
}
223+
206224
/**
207225
* generateTree - Generates tree elements (`nodes` and `links`) by
208226
* grabbing the rootNode from `this.state.data[0]`.
@@ -289,6 +307,7 @@ export default class Tree extends React.Component {
289307
name={nodeData.name}
290308
attributes={nodeData.attributes}
291309
onClick={this.handleNodeToggle}
310+
onMouseOver={this.handleOnMouseOverCb}
292311
textLayout={textLayout}
293312
circleRadius={circleRadius}
294313
subscriptions={subscriptions}
@@ -310,6 +329,7 @@ Tree.defaultProps = {
310329
},
311330
},
312331
onClick: undefined,
332+
onMouseOver: undefined,
313333
orientation: 'horizontal',
314334
translate: { x: 0, y: 0 },
315335
pathFunc: 'diagonal',
@@ -338,6 +358,7 @@ Tree.propTypes = {
338358
shapeProps: PropTypes.object,
339359
}),
340360
onClick: PropTypes.func,
361+
onMouseOver: PropTypes.func,
341362
orientation: PropTypes.oneOf(['horizontal', 'vertical']),
342363
translate: PropTypes.shape({
343364
x: PropTypes.number,

src/Tree/tests/index.test.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ describe('<Tree />', () => {
165165
expect(onClickSpy).toHaveBeenCalledTimes(1);
166166
});
167167

168-
it('calls the onClick callback when `props.collapsible` is false', () => {
168+
it('calls the onClick callback even when `props.collapsible` is false', () => {
169169
const onClickSpy = jest.fn();
170170
const renderedComponent = mount(
171171
<Tree data={mockData} collapsible={false} onClick={onClickSpy} />,
@@ -195,4 +195,33 @@ describe('<Tree />', () => {
195195
.prop('nodeData'),
196196
);
197197
});
198+
199+
it('calls the onMouseOver callback when a node is hovered over', () => {
200+
const onMouseOverSpy = jest.fn();
201+
const renderedComponent = mount(<Tree data={mockData} onMouseOver={onMouseOverSpy} />);
202+
203+
renderedComponent
204+
.find(Node)
205+
.first()
206+
.simulate('mouseover');
207+
208+
expect(onMouseOverSpy).toHaveBeenCalledTimes(1);
209+
});
210+
211+
it("clones the hovered node's data & passes it to the onMouseOver callback if defined", () => {
212+
const onMouseOverSpy = jest.fn();
213+
const renderedComponent = mount(<Tree data={mockData} onMouseOver={onMouseOverSpy} />);
214+
215+
renderedComponent
216+
.find(Node)
217+
.first()
218+
.simulate('mouseover');
219+
220+
expect(onMouseOverSpy).toHaveBeenCalledWith(
221+
renderedComponent
222+
.find(Node)
223+
.first()
224+
.prop('nodeData'),
225+
);
226+
});
198227
});

0 commit comments

Comments
 (0)