import React from 'react' const Release = React.createClass({ render() { const { title, artist, outOfPrint } = this.props.release; const className = outOfPrint? 'release outOfPrint' : 'release'; return ( <tr className={className} > <td className="artist">{ artist }</td> <td className="title">{ title }</td> <td className="comment">{ outOfPrint ? <span style={{color: 'red', fontStyle: 'italic'}}>Out Of Print!</span> : null }</td> </tr> ); } }); const ReleaseTable = React.createClass({ render() { const { searchText, releases, noOutOfPrint } = this.props; const rows = releases.filter((release) => { return (release.title.indexOf(searchText) !== -1 || release.artist.indexOf(searchText) !== -1) && !(release.outOfPrint && noOutOfPrint); }); return ( <table className="releaseTable"> <thead> <tr> <th>Artist</th> <th>Title</th> <th>Comment</th> </tr> </thead> <tbody>{rows.map(release => { return <Release release={ release } key={ release.title } /> })}</tbody> </table> ); } }); const SearchBar = React.createClass({ updateSearch() { this.props.onSearch( this.refs.search.value, this.refs.noOutOfPrint.checked ); }, render() { return ( <form className="searchBar"> <input type="text" placeholder="Search..." value={this.props.searchText} ref="search" onChange={this.updateSearch} id="searchFilter" /> <p> <input type="checkbox" checked={this.props.noOutOfPrint} ref="noOutOfPrint" onChange={this.updateSearch} id="noOutOfPrint" /> {' '} Only show available releases </p> </form> ); } }); const Root = React.createClass({ getInitialState() { return { searchText: '', noOutOfPrint: false }; }, updateSearch: function (searchText, noOutOfPrint) { this.setState({ searchText, noOutOfPrint }); }, render() { return ( <div className="main"> <SearchBar searchText={this.state.searchText} inStockOnly={this.state.noOutOfPrint} onSearch={this.updateSearch} /> <ReleaseTable releases={this.props.releases} searchText={this.state.searchText} noOutOfPrint={this.state.noOutOfPrint} /> </div> ); } }); export { Release, ReleaseTable, SearchBar }; export default Root;
import React from 'react' import { shallow, mount, render } from 'enzyme' import Root, { SearchBar, ReleaseTable, Release } from '../src/Root' const createShallowRelease = (outOfPrint = true) => { let props = {release: { artist: 'foobar', title: 'bar', outOfPrint }}; return shallow(<Release {...props} />); }; const createShallowRelaseTable = (noOutOfPrint = false, searchText = '') => { let items = [{ artist: 'foobar', title: 'bar', outOfPrint: true }]; let props = { searchText, releases: items, noOutOfPrint }; return shallow(<ReleaseTable { ...props } />); } describe('<SearchBar>', () => { let onSearch; beforeEach(() => { onSearch = jasmine.createSpy('onSearch'); }); it('calls onSearch when search text changes', () => { let props = { searchText: '', noOutOfPrint: false, onSearch }; let search = mount(<SearchBar { ...props } />); let input = search.find('#searchFilter'); input.get(0).value = 'foobar'; input.simulate('change'); expect(onSearch).toHaveBeenCalledWith('foobar', false); }); it('calls onSearch when no out print is checked', () => { let props = { searchText: '', noOutOfPrint: false, onSearch }; let search = mount(<SearchBar { ...props } />); let input = search.find('#noOutOfPrint'); input.get(0).checked = true; input.simulate('change', {target: { checked: true }}); expect(onSearch).toHaveBeenCalledWith('', true); }); }); describe('<ReleaseTable>', () => { it('contains the class name releaseTable', () => { let release = createShallowRelaseTable(); expect(release.is('.releaseTable')).toBeTruthy(); }); it('renders release item', () => { let release = createShallowRelaseTable(); expect(release.find(Release).length).toBe(1); }); it('filters out any out of print items', () => { let release = createShallowRelaseTable(true, ''); expect(release.find(Release).length).toBe(0); }); it('filters out any out of items when filtering by search text', () => { let release = createShallowRelaseTable(false, 'bla'); expect(release.find(Release).length).toBe(0); }); }); describe('<Release>', () => { it('contains the class name release', () => { let release = createShallowRelease(); expect(release.is('.release')).toBeTruthy(); }); it ('contains the class name outOfPrint if out of print', () => { let release = createShallowRelease(true); expect(release.is('.outOfPrint')).toBeTruthy(); }); it ('does not contain the class name outOfPrint if available', () => { let release = createShallowRelease(false); expect(release.is('.outOfPrint')).toBeFalsy(); }); it ('renders the artist name', () => { let release = createShallowRelease(); expect(release.find('.artist').text()).toEqual('foobar'); }); it ('renders the release title', () => { let release = createShallowRelease(); expect(release.find('.title').text()).toEqual('bar'); }); it ('renders the correct comment', () => { let release = createShallowRelease(true); expect(release.find('.comment').text()).toEqual('Out Of Print!'); }); });
{ "name": "example-karma-jasmine-webapck-test-setup", "description": "React Test Setup with Karma/Jasmine/Webpack", "scripts": { "test": "karma start --single-run --browsers PhantomJS" }, "devDependencies": { "babel": "^6.5.2", "babel-core": "^6.5.2", "babel-eslint": "^5.0.0", "babel-loader": "^6.2.3", "babel-preset-airbnb": "^1.1.1", "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", "enzyme": "^2.0.0", "jasmine-core": "^2.4.1", "json-loader": "^0.5.4", "karma": "^0.13.21", "karma-babel-preprocessor": "^6.0.1", "karma-jasmine": "^0.3.7", "karma-phantomjs-launcher": "^1.0.0", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^1.7.0", "lodash": "^4.5.1", "phantomjs-prebuilt": "^2.1.4", "react": "^0.14.7", "react-addons-test-utils": "^0.14.7", "react-dom": "^0.14.7", "react-test-utils": "0.0.1", "webpack": "^1.12.14" } }