Skip to content

Commit e5cf741

Browse files
committed
Fixes possible orphan nodes when toggling (#8)
1 parent 6179ca1 commit e5cf741

File tree

2 files changed

+51
-7
lines changed

2 files changed

+51
-7
lines changed

src/Tree/index.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default class Tree extends React.Component {
1919
this.internalState = {
2020
initialRender: true,
2121
targetNode: null,
22+
isTransitioning: false,
2223
d3: {
2324
scale: this.props.zoom,
2425
translate: this.props.translate,
@@ -226,9 +227,15 @@ export default class Tree extends React.Component {
226227
const matches = this.findNodesById(nodeId, data, []);
227228
const targetNode = matches[0];
228229

229-
if (this.props.collapsible) {
230+
if (this.props.collapsible && !this.state.isTransitioning) {
230231
targetNode._collapsed ? this.expandNode(targetNode) : this.collapseNode(targetNode);
231-
this.setState({ data }, () => this.handleOnClickCb(targetNode, evt));
232+
// Lock node toggling while transition takes place
233+
this.setState({ data, isTransitioning: true }, () => this.handleOnClickCb(targetNode, evt));
234+
// Await transitionDuration + 10 ms before unlocking node toggling again
235+
setTimeout(
236+
() => this.setState({ isTransitioning: false }),
237+
this.props.transitionDuration + 10,
238+
);
232239
this.internalState.targetNode = targetNode;
233240
} else {
234241
this.handleOnClickCb(targetNode, evt);

src/Tree/tests/index.test.js

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,15 @@ describe('<Tree />', () => {
165165
});
166166

167167
it("expands a node's children when it is clicked in a collapsed state", () => {
168+
jest.useFakeTimers();
168169
const renderedComponent = mount(<Tree data={mockData} />);
169170
renderedComponent
170171
.find(Node)
171172
.first()
172173
.simulate('click'); // collapse
174+
175+
jest.runAllTimers();
176+
173177
renderedComponent
174178
.find(Node)
175179
.first()
@@ -190,6 +194,43 @@ describe('<Tree />', () => {
190194
expect(Tree.prototype.handleNodeToggle).toHaveBeenCalledTimes(1);
191195
expect(Tree.prototype.collapseNode).toHaveBeenCalledTimes(0);
192196
});
197+
198+
it('does not toggle any nodes again until `transitionDuration` has completed', () => {
199+
const renderedComponent = mount(<Tree data={mockData} />);
200+
renderedComponent
201+
.find(Node)
202+
.first()
203+
.simulate('click');
204+
205+
renderedComponent
206+
.find(Node)
207+
.first()
208+
.simulate('click');
209+
210+
expect(Tree.prototype.handleNodeToggle).toHaveBeenCalledTimes(2);
211+
expect(Tree.prototype.collapseNode).toHaveBeenCalled();
212+
expect(Tree.prototype.expandNode).not.toHaveBeenCalled();
213+
});
214+
215+
it('allows toggling nodes again after `transitionDuration` + 10ms has expired', () => {
216+
jest.useFakeTimers();
217+
const renderedComponent = mount(<Tree data={mockData} />);
218+
renderedComponent
219+
.find(Node)
220+
.first()
221+
.simulate('click');
222+
223+
jest.runAllTimers();
224+
225+
renderedComponent
226+
.find(Node)
227+
.first()
228+
.simulate('click');
229+
230+
expect(Tree.prototype.handleNodeToggle).toHaveBeenCalledTimes(2);
231+
expect(Tree.prototype.collapseNode).toHaveBeenCalled();
232+
expect(Tree.prototype.expandNode).toHaveBeenCalledTimes(1);
233+
});
193234
});
194235

195236
describe('initialDepth', () => {
@@ -384,12 +425,8 @@ describe('<Tree />', () => {
384425
.find(Node)
385426
.first()
386427
.simulate('click'); // collapse
387-
renderedComponent
388-
.find(Node)
389-
.first()
390-
.simulate('click'); // re-expand
391428

392-
expect(onUpdateSpy).toHaveBeenCalledTimes(2);
429+
expect(onUpdateSpy).toHaveBeenCalledTimes(1);
393430
expect(onUpdateSpy).toHaveBeenCalledWith({
394431
node: expect.any(Object),
395432
zoom: 1,

0 commit comments

Comments
 (0)