Skip to content

Commit 0368332

Browse files
committed
test(*): add unit tests
1 parent fb78e4c commit 0368332

File tree

11 files changed

+277
-16
lines changed

11 files changed

+277
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* eslint-disable no-unused-expressions, react/prop-types */
2+
import { expect } from 'chai';
3+
4+
import React from 'react';
5+
import { shallow } from 'enzyme';
6+
import Profile from '../Profile';
7+
import Panel from '../../common/Panel';
8+
import DisplayInfosPanel from '../../common/DisplayInfosPanel';
9+
10+
const mockProfile = {
11+
data: {
12+
html_url: 'https://github.com/topheman',
13+
login: 'topheman',
14+
avatar_url: 'https://avatars.githubusercontent.com/u/985982?v=3',
15+
name: 'Christophe Rosset',
16+
location: 'Paris',
17+
created_at: '2011-08-17T11:59:42Z',
18+
blog: 'http://labs.topheman.com/',
19+
followers: 37,
20+
following: 2,
21+
bio: 'JavaScript FTW'
22+
}
23+
};
24+
25+
describe('components/Profile', () => {
26+
describe('state/render', () => {
27+
describe('profile fully hydrated', () => {
28+
const wrapper = shallow(<Profile profile={mockProfile} />);
29+
it('should render a Panel with props.profile.data.login as title', () => {
30+
expect(wrapper.find(Panel).props().title).to.be.equal('topheman');
31+
});
32+
it('should render a proper anchor with link & title', () => {
33+
const a = wrapper.find('a').first();
34+
expect(a.props().href).to.be.equal('https://github.com/topheman');
35+
expect(a.props().title).to.be.equal('Visit topheman profile on Github');
36+
});
37+
it('should render a proper image profile with correct avatar url (adapted)', () => {
38+
expect(wrapper.find('img').first().props().src).to.be.equal('https://avatars.githubusercontent.com/u/985982?v=3&s=130');
39+
});
40+
});
41+
describe('profile not fully hydrated', () => {
42+
it('should return a placeholder panel', () => {
43+
const wrapper = shallow(<Profile profile={{pristineLogin: 'topheman'}} />);
44+
expect(wrapper.equals(<DisplayInfosPanel infos={{pristineLogin: 'topheman'}} originalTitle="topheman" />)).to.be.true;
45+
});
46+
});
47+
});
48+
});

src/components/ProfileBox/ProfileBox.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ const ProfileBox = ({user}) => {
1212
};
1313

1414
ProfileBox.propTypes = {
15-
user: React.PropTypes.object.isRequired
15+
user: React.PropTypes.shape({
16+
login: React.PropTypes.string.isRequired,
17+
$avatar_url: React.PropTypes.string.isRequired
18+
})
1619
};
1720

1821
export default ProfileBox;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* eslint-disable no-unused-expressions, react/prop-types */
2+
import { expect } from 'chai';
3+
4+
import React from 'react';
5+
import { shallow } from 'enzyme';
6+
import ProfileBox from '../ProfileBox';
7+
import { Link } from 'react-router';
8+
9+
const mockUser = {
10+
login: 'topheman',
11+
$avatar_url: 'https://avatars.githubusercontent.com/u/985982?v=3&s=130'
12+
};
13+
14+
describe('components/ProfileBox', () => {
15+
describe('state/render', () => {
16+
const wrapper = shallow(<ProfileBox user={mockUser} />);
17+
it('should render link with correct props', () => {
18+
expect(wrapper.find(Link).prop('to')).to.be.equal('/github/user/topheman');
19+
});
20+
it('should render an img with src same as props.user.$avatar_url', () => {
21+
expect(wrapper.contains(<img src="https://avatars.githubusercontent.com/u/985982?v=3&s=130" width="40"/>)).to.be.true;
22+
});
23+
it('should render the user login', () => {
24+
expect(wrapper.contains(<strong>topheman</strong>)).to.be.true;
25+
});
26+
});
27+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* eslint-disable no-unused-expressions, react/prop-types */
2+
import { expect } from 'chai';
3+
4+
import React from 'react';
5+
import { shallow } from 'enzyme';
6+
import ProfileList from '../ProfileList';
7+
import ProfileBox from '../../ProfileBox/ProfileBox';
8+
9+
describe('components/ProfileList', () => {
10+
describe('state/render', () => {
11+
describe('case props.results not hydrated with data', () => {
12+
it('should render default message if props.results=null', () => {
13+
const wrapper = shallow(<ProfileList results={null} />);
14+
expect(wrapper.type()).to.be.equal('p');
15+
});
16+
it('should render the error if props.results.error is set', () => {
17+
const wrapper = shallow(<ProfileList results={{error: 'Something went wrong'}} />);
18+
expect(wrapper.equals(<div>Something went wrong</div>)).to.be.true;
19+
});
20+
it('should show "No results" if props.results.total_count=0', () => {
21+
const wrapper = shallow(<ProfileList results={{total_count: 0}} />);
22+
expect(wrapper.equals(<div>No results.</div>)).to.be.true;
23+
});
24+
});
25+
describe('case props.results hydrated with data', () => {
26+
it('header should show "Total result" in case props.results.total_count=1', () => {
27+
const wrapper = shallow(<ProfileList results={{total_count: 1, items: [{$avatar_url: 'someurl', id: 42, login: 'foo'}] }} />);
28+
expect(wrapper.find('.panel-heading').text()).to.equal('Total result : 1 / showing : 1');
29+
});
30+
it('header should show "Total results" in case props.results.total_count>1', () => {
31+
const wrapper = shallow(<ProfileList results={{total_count: 2, items: [{$avatar_url: 'someurl', id: 42, login: 'foo'}, {$avatar_url: 'someurl', id: 43, login: 'bar'}] }} />);
32+
expect(wrapper.find('.panel-heading').text()).to.equal('Total results : 2 / showing : 2');
33+
});
34+
it('should render correctly the ProfileBox in the list', () => {
35+
const wrapper = shallow(<ProfileList results={{total_count: 1, items: [{$avatar_url: 'someurl', id: 42, login: 'foo'}] }} />);
36+
expect(wrapper.find(ProfileBox).first().prop('user').id).to.equal(42);
37+
});
38+
it('should render correctly multiple ProfileBox in the list', () => {
39+
const wrapper = shallow(<ProfileList results={{total_count: 1, items: [{$avatar_url: 'someurl', id: 42, login: 'foo'}, {$avatar_url: 'someurl', id: 43, login: 'bar'}] }} />);
40+
expect(wrapper.find(ProfileBox).length).to.equal(2);
41+
});
42+
});
43+
});
44+
});

src/components/Repos/Repos.js

+3-14
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,16 @@ export default class Repos extends React.Component {
1212
reposGotoPage: React.PropTypes.func.isRequired
1313
}
1414

15-
constructor(props) {
16-
17-
super(props);
18-
19-
// init context bindings - due to diff between React.createClass and ES6 class
20-
this.reposGotoPage = this.reposGotoPage.bind(this);
21-
22-
}
23-
reposGotoPage(pageNum) {
24-
this.props.reposGotoPage(pageNum);
25-
}
2615
render() {
27-
const { repositories } = this.props;
16+
const { repositories, reposGotoPage } = this.props;
2817
const fetching = repositories.fetching;
2918
const originalTitle = repositories.pristineLogin + "'s repositories";
3019
if (repositories && repositories.data) {
3120
const repos = repositories.data;
3221
return (
3322
<Panel title={originalTitle}>
3423
<div className="panel-body repos-list">
35-
<ReposPaginator infos={repositories.infos} reposGotoPage={this.reposGotoPage} fetching={fetching}/>
24+
<ReposPaginator infos={repositories.infos} reposGotoPage={reposGotoPage} fetching={fetching}/>
3625
<div className="list-group">
3726
{repos.map((repo) => {
3827
return (
@@ -45,7 +34,7 @@ export default class Repos extends React.Component {
4534
);
4635
})}
4736
</div>
48-
<ReposPaginator infos={repositories.infos} reposGotoPage={this.reposGotoPage} fetching={fetching}/>
37+
<ReposPaginator infos={repositories.infos} reposGotoPage={reposGotoPage} fetching={fetching}/>
4938
</div>
5039
</Panel>
5140
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* eslint-disable no-unused-expressions, react/prop-types */
2+
import { expect } from 'chai';
3+
4+
import React from 'react';
5+
import { shallow } from 'enzyme';
6+
import Repos from '../Repos';
7+
import DisplayInfosPanel from '../../common/DisplayInfosPanel';
8+
import DisplayStars from '../../common/DisplayStars';
9+
10+
function noop() {}
11+
12+
describe('components/ProfileBox', () => {
13+
describe('state/render', () => {
14+
it('should render a placeholder if props.repositories.data not hydrated', () => {
15+
const mockRepositories = { fetching: false, pristineLogin: 'topheman' };
16+
const wrapper = shallow(<Repos repositories={mockRepositories} reposGotoPage={noop} />);
17+
expect(wrapper.equals(<DisplayInfosPanel infos={mockRepositories} originalTitle="topheman's repositories"/>)).to.be.true;
18+
});
19+
describe('check render for list of repositories', () => {
20+
const mockRepositories = {
21+
fetching: false,
22+
infos: {},
23+
pristineLogin: 'topheman',
24+
data: [
25+
{ html_url: 'https://github.com/topheman/react-es6-redux', name: 'react-es6-redux', stargazers_count: 57, full_name: 'topheman/react-es6-redux' },
26+
{ html_url: 'https://github.com/topheman/webpack-babel-starter', name: 'webpack-babel-starter', stargazers_count: 39, full_name: 'topheman/webpack-babel-starter' },
27+
{ html_url: 'https://github.com/topheman/rxjs-experiments', name: 'rxjs-experiments', stargazers_count: 19, full_name: 'topheman/rxjs-experiments' }
28+
]
29+
};
30+
const wrapper = shallow(<Repos repositories={mockRepositories} reposGotoPage={noop} />);
31+
it('should render the correct amount of links to repos', () => {
32+
expect(wrapper.find('a').length).to.equal(3);
33+
});
34+
it('should render links properly', () => {
35+
const anchor = wrapper.find('a').get(1);
36+
expect(anchor.props.href).to.equal('https://github.com/topheman/webpack-babel-starter');
37+
expect(anchor.props.title).to.equal('topheman/webpack-babel-starter');
38+
});
39+
it('should render stargazer properly', () => {
40+
const displayStars = wrapper.find(DisplayStars).get(1);
41+
expect(displayStars.props.number).to.equal(39);
42+
});
43+
});
44+
describe('check paginators', () => {
45+
46+
});
47+
});
48+
});

src/components/common/__tests__/DisplayInfosPanel.spec.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ import Panel from '../Panel';
99

1010
describe('components/common/DisplayInfosPanel', () => {
1111
describe('state/render', () => {
12-
it('should render a spinner when props.infos.fetching=true', () => {
12+
it('should render a spinner at fetching=true when props.infos.fetching=true', () => {
1313
const wrapper = shallow(<DisplayInfosPanel infos={{fetching: true}} originalTitle={""} />);
1414
expect(wrapper.find(Spinner).props().fetching).to.be.true;
1515
});
16+
it('should render a spinner at fetching=false when props.infos.fetching=false', () => {
17+
const wrapper = shallow(<DisplayInfosPanel infos={{fetching: false}} originalTitle={""} />);
18+
expect(wrapper.find(Spinner).props().fetching).to.be.false;
19+
});
1620
it('should render a panel with a title=props.originalTitle when props.infos.fetching=true', () => {
1721
const wrapper = shallow(<DisplayInfosPanel infos={{fetching: true}} originalTitle={"Hello World"} />);
1822
expect(wrapper.find(Panel).props().title).to.equal('Hello World');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* eslint-disable no-unused-expressions, react/prop-types */
2+
import { expect } from 'chai';
3+
4+
import React from 'react';
5+
import { shallow } from 'enzyme';
6+
import Panel from '../Panel';
7+
8+
describe('components/common/Panel', () => {
9+
describe('state/render', () => {
10+
it('should render title', () => {
11+
const wrapper = shallow(<Panel title="Hello World"><div>Hie there</div></Panel>);
12+
expect(wrapper.contains(<h3 className="panel-title">Hello World</h3>)).to.be.true;
13+
});
14+
it('should render children', () => {
15+
const wrapper = shallow(<Panel title="Hello World"><div>Hie there</div></Panel>);
16+
expect(wrapper.contains(<div>Hie there</div>)).to.be.true;
17+
});
18+
});
19+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* eslint-disable no-unused-expressions, react/prop-types */
2+
import { expect } from 'chai';
3+
4+
import React from 'react';
5+
import { shallow } from 'enzyme';
6+
import Spinner from '../Spinner';
7+
8+
describe('components/common/Spinner', () => {
9+
describe('state/render', () => {
10+
it('should render empty div when props.fetching!==true', () => {
11+
const wrapper = shallow(<Spinner />);
12+
expect(wrapper.find('.bounce1').length).to.equal(0);
13+
});
14+
it('should render nested divs when props.fetching=true', () => {
15+
const wrapper = shallow(<Spinner fetching />);
16+
expect(wrapper.find('.bounce1').length).to.equal(1);
17+
expect(wrapper.find('.bounce2').length).to.equal(1);
18+
expect(wrapper.find('.bounce3').length).to.equal(1);
19+
});
20+
});
21+
});
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* eslint-disable no-unused-expressions, react/prop-types */
2+
import { expect } from 'chai';
3+
4+
import React from 'react';
5+
import { shallow } from 'enzyme';
6+
import Tr from '../Tr';
7+
8+
describe('components/common/Tr', () => {
9+
describe('state/render', () => {
10+
it('should render 2 columns by default', () => {
11+
const wrapper = shallow(<Tr label="Hello" value="World" />);
12+
expect(wrapper.find('td').length).to.equal(2);
13+
expect(wrapper.contains(<td>Hello</td>)).to.be.true;
14+
expect(wrapper.contains(<td>World</td>)).to.be.true;
15+
});
16+
it('should render in 1 column if props.display=colspan', () => {
17+
const wrapper = shallow(<Tr value="Hello World" display="colspan" />);
18+
expect(wrapper.find('td').length).to.equal(1);
19+
expect(wrapper.contains(<td colSpan="2">Hello World</td>)).to.be.true;
20+
});
21+
it('should render value as a link if props.type=link', () => {
22+
const wrapper = shallow(<Tr value="http://labs.topheman.com" label="Website" type="link" />);
23+
expect(wrapper.contains(<a href="http://labs.topheman.com">http://labs.topheman.com</a>)).to.be.true;
24+
});
25+
it('should render value as date (without hours/min/sec) if value is instance of a Date', () => {
26+
const value = new Date('2014-04-24');
27+
const expected = 'Thu Apr 24 2014';
28+
const wrapper = shallow(<Tr value={value} label="Date" />);
29+
expect(wrapper.contains(<td>{expected}</td>)).to.be.true;
30+
});
31+
it('should render no nested td if no props.value passed', () => {
32+
const wrapper = shallow(<Tr />);
33+
expect(wrapper.find('td').length).to.equal(0);
34+
});
35+
});
36+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* eslint-disable no-unused-expressions, react/prop-types */
2+
import { expect } from 'chai';
3+
4+
import { default as reducer, increment } from '../counter';
5+
6+
const INCREMENT = 'counter/INCREMENT';
7+
8+
describe('redux/modules/counter', () => {
9+
describe('reducer', () => {
10+
it('should return default state = 0', () => {
11+
expect(reducer()).to.equal(0);
12+
});
13+
it('should return increment state on INCREMENT action', () => {
14+
expect(reducer(2, {type: INCREMENT})).to.equal(3);
15+
});
16+
});
17+
describe('action creators', () => {
18+
it('increment() should return proper action', () => {
19+
expect(increment()).to.eql({type: INCREMENT});
20+
});
21+
});
22+
});

0 commit comments

Comments
 (0)