Skip to content

Commit 0524828

Browse files
committed
Merge branch 'feature/add-transitions' into develop
2 parents 7cf6661 + bf5f3dc commit 0524828

File tree

10 files changed

+426
-183
lines changed

10 files changed

+426
-183
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
language: node_js
22

33
node_js:
4-
- 8
54
- 7
65
- 6
76
- 5
87

98
script:
10-
- npm run lint
119
- npm run test
1210
- npm run build
1311

package-lock.json

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"coverageThreshold": {
3030
"global": {
3131
"statements": 90,
32-
"branches": 89,
32+
"branches": 83,
3333
"functions": 90,
3434
"lines": 90
3535
}
@@ -69,6 +69,7 @@
6969
"react": "15.4.2",
7070
"react-addons-test-utils": "15.4.2",
7171
"react-dom": "15.4.2",
72+
"react-transition-group": "^1.1.3",
7273
"regenerator-runtime": "^0.10.5",
7374
"rimraf": "^2.6.1",
7475
"style-loader": "^0.17.0",

src/Link/index.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,39 @@
11
import React, { PropTypes } from 'react';
2-
import * as d3 from 'd3';
2+
import { svg, select } from 'd3';
33

44
import './style.css';
55

66
export default class Link extends React.PureComponent {
77

88
constructor(props) {
99
super(props);
10-
this.generatePathDescription = this.generatePathDescription.bind(this);
10+
this.state = {
11+
initialStyle: {
12+
opacity: 0,
13+
},
14+
};
15+
}
16+
17+
componentDidMount() {
18+
this.applyOpacity(1);
19+
}
20+
21+
componentWillLeave(done) {
22+
this.applyOpacity(0, done);
23+
}
24+
25+
applyOpacity(opacity, done = () => {}) {
26+
const { transitionDuration } = this.props;
27+
28+
select(this.link)
29+
.transition()
30+
.duration(transitionDuration)
31+
.style('opacity', opacity)
32+
.each('end', done);
1133
}
1234

1335
diagonalPath(linkData, orientation) {
14-
const diagonal = d3.svg.diagonal().projection((d) =>
36+
const diagonal = svg.diagonal().projection((d) =>
1537
orientation === 'horizontal' ? [d.y, d.x] : [d.x, d.y]
1638
);
1739
return diagonal(linkData);
@@ -23,7 +45,7 @@ export default class Link extends React.PureComponent {
2345
`M${d.source.x},${d.source.y}V${d.target.y}H${d.target.x}`;
2446
}
2547

26-
generatePathDescription() {
48+
drawPath() {
2749
const { linkData, orientation, pathFunc } = this.props;
2850
return pathFunc === 'diagonal'
2951
? this.diagonalPath(linkData, orientation)
@@ -33,8 +55,10 @@ export default class Link extends React.PureComponent {
3355
render() {
3456
return (
3557
<path
58+
ref={(l) => { this.link = l; }}
59+
style={this.state.initialStyle}
3660
className="linkBase"
37-
d={this.generatePathDescription()}
61+
d={this.drawPath()}
3862
/>
3963
);
4064
}
@@ -50,4 +74,5 @@ Link.propTypes = {
5074
'diagonal',
5175
'elbow',
5276
]).isRequired,
77+
transitionDuration: PropTypes.number.isRequired,
5378
};

src/Link/tests/index.test.js

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { shallow } from 'enzyme';
2+
import { shallow, mount } from 'enzyme';
33

44
import Link from '../index';
55

@@ -15,49 +15,65 @@ describe('<Link />', () => {
1515
},
1616
};
1717

18-
// Clear method spies on prototype before next test
18+
const mockProps = {
19+
linkData,
20+
pathFunc: 'diagonal',
21+
orientation: 'horizontal',
22+
transitionDuration: 500,
23+
};
24+
25+
26+
jest.spyOn(Link.prototype, 'diagonalPath');
27+
jest.spyOn(Link.prototype, 'elbowPath');
28+
jest.spyOn(Link.prototype, 'applyOpacity');
29+
30+
// Clear method spies on prototype after each test
1931
afterEach(() => jest.clearAllMocks());
2032

21-
it('should apply the base className', () => {
33+
34+
it('applies the base className', () => {
2235
const renderedComponent = shallow(
23-
<Link
24-
linkData={linkData}
25-
pathFunc="diagonal"
26-
orientation="horizontal"
27-
/>
36+
<Link {...mockProps} />
2837
);
2938

3039
expect(renderedComponent.prop('className')).toBe('linkBase');
3140
});
3241

33-
it('should call the appropriate path func based on `props.pathFunc`', () => {
34-
jest.spyOn(Link.prototype, 'diagonalPath');
35-
jest.spyOn(Link.prototype, 'elbowPath');
42+
43+
it('calls the appropriate path func based on `props.pathFunc`', () => {
3644
const diagonalComponent = shallow(
37-
<Link
38-
linkData={linkData}
39-
pathFunc="diagonal"
40-
orientation="horizontal"
41-
/>
45+
<Link {...mockProps} />
4246
);
4347
const elbowComponent = shallow(
4448
<Link
45-
linkData={linkData}
49+
{...mockProps}
4650
pathFunc="elbow"
47-
orientation="horizontal"
4851
/>
4952
);
5053

5154
expect(diagonalComponent.instance().diagonalPath).toHaveBeenCalled();
5255
expect(elbowComponent.instance().elbowPath).toHaveBeenCalled();
5356
});
5457

55-
it('should return an appropriate elbowPath according to `props.orientation`', () => {
58+
59+
it('returns an appropriate elbowPath according to `props.orientation`', () => {
5660
expect(
5761
Link.prototype.elbowPath(linkData, 'horizontal')
5862
).toBe(`M${linkData.source.y},${linkData.source.x}V${linkData.target.x}H${linkData.target.y}`);
5963
expect(
6064
Link.prototype.elbowPath(linkData, 'vertical')
6165
).toBe(`M${linkData.source.x},${linkData.source.y}V${linkData.target.y}H${linkData.target.x}`);
6266
});
67+
68+
69+
it('fades in once it has been mounted', () => {
70+
const fixture = 1;
71+
const renderedComponent = mount(
72+
<Link {...mockProps} />
73+
);
74+
75+
expect(renderedComponent.instance().applyOpacity).toHaveBeenCalledWith(fixture);
76+
});
77+
78+
// TODO Find a way to meaningfully test `componentWillLeave`
6379
});

0 commit comments

Comments
 (0)