diff --git a/README.md b/README.md index 5f90251..672a837 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# React seed [![Build Status](https://travis-ci.org/badsyntax/react-seed.svg?branch=master)](https://travis-ci.org/badsyntax/react-seed) +# React seed [![Circle CI](https://circleci.com/gh/continuata/react-seed.svg?style=svg)](https://circleci.com/gh/continuata/react-seed) A boilerplate for building React apps with ES6 and webpack. @@ -12,6 +12,7 @@ A boilerplate for building React apps with ES6 and webpack. * Basic flux architecture with app actions, stores and example web API usage * React router ([feature/react-router](https://github.com/badsyntax/react-seed/tree/feature/react-router)) * Material UI ([feature/material-ui](https://github.com/badsyntax/react-seed/tree/feature/material-ui)) +* Standard JS for linting ## Getting started @@ -40,32 +41,30 @@ git clone --depth=1 https://github.com/badsyntax/react-seed.git my-project ```js // Filename: Menu.jsx -'use strict'; +import styles from './_Menu.scss' +import React from 'react' +import MenuItem from './MenuItem' -import styles from './_Menu.scss'; -import React from 'react'; -import MenuItem from './MenuItem'; - -let { Component, PropTypes } = React; +let { Component, PropTypes } = React export default class Menu extends Component { static defaultProps = { items: [] - }; + } static propTypes = { items: PropTypes.array.isRequired - }; + } - render() { + render () { return ( - ); + ) } } ``` @@ -74,70 +73,71 @@ export default class Menu extends Component { ```js // Filename: __tests__/Menu-test.jsx +import React from 'react/addons' +import { expect } from 'chai' -'use strict'; - -import React from 'react/addons'; -import { expect } from 'chai'; - -import Menu from '../Menu.jsx'; -import MenuItem from '../MenuItem.jsx'; - -// Here we create a mocked MenuItem component. -class MockedMenuItem extends MenuItem { - render() { - return ( -
  • {this.props.item.label}
  • - ); - } -} - -// Here we set the mocked MenuItem component. -Menu.__Rewire__('MenuItem', MockedMenuItem); +import Menu from '../Menu.jsx' describe('Menu', () => { - - let { TestUtils } = React.addons; + let { TestUtils } = React.addons let menuItems = [ { id: 1, label: 'Option 1' }, { id: 2, label: 'Option 2' } - ]; - - let menu = TestUtils.renderIntoDocument( - - ); - let menuElem = React.findDOMNode(menu); - let items = menuElem.querySelectorAll('li'); - - it('Should render the menu items', () => { - expect(items.length).to.equal(2); - }); - - it('Should render the menu item labels', () => { - Array.prototype.forEach.call(items, (item, i) => { - expect(item.textContent.trim()).to.equal(menuItems[i].label); - }); + ] + + describe('Rendering', () => { + // Here we create a mocked MenuItem component. + let menu = TestUtils.renderIntoDocument( + + ) + let menuElem = React.findDOMNode(menu) + let items = menuElem.querySelectorAll('li') + + it('Should render the menu items', () => { + expect(items.length).to.equal(2) + }) + + it('Should render the menu item labels', () => { + Array.prototype.forEach.call(items, (item, i) => { + expect(item.textContent.trim()).to.equal(menuItems[i].label) + }) + }) }) - it('Should render the mocked menu item', () => { - expect(items[0].className).to.equal('mocked-menu-item'); - }); -}); - + describe('Events', (done) => { + // Example of simulating browser events. + it('Should handle click events', () => { + let menu = TestUtils.renderIntoDocument( + + ) + let menuElem = React.findDOMNode(menu) + let items = menuElem.querySelectorAll('li') + let node = items[0].querySelector('a') + + window.onAlert = function (alertText) { + expect(alertText).to.equal("ALERT: 'You clicked Option 1'") + done() + } + + TestUtils.Simulate.click(node) + }) + }) +}) ``` -## Sass, CSS & webpack +## Sass, Stylus, CSS & webpack -`import` Sass and CSS files from within your JavaScript component files: +`import` Sass, Stylus and CSS files from within your JavaScript component files: ```js // Filename: app.jsx import 'normalize.css/normalize.css'; import styles from './scss/app.scss'; +import styles from './styl/app.styl'; ``` -* **Note:** If you're importing component Sass files from within your JavaScript component files, then each sass file will be compiled as part of a different compile process, and thus you cannot share global references. See [this issue](https://github.com/jtangelder/sass-loader/issues/105) for more information. +* **Note:** If you're importing component Sass, Styl files from within your JavaScript component files, then each sass file will be compiled as part of a different compile process, and thus you cannot share global references. See [this issue](https://github.com/jtangelder/sass-loader/issues/105) for more information. * Sass include paths can be adjusted in the `webpack/loaders.js` file. * All CSS (compiled or otherwise) is run through Autoprefixer and style-loader. Any images/fonts etc referenced in the CSS will be copied to the build dir. * CSS files are combined in the order in which they are imported in JavaScript, thus diff --git a/app/actions/AppActions.js b/app/actions/AppActions.js index 04ae62c..b3b2ad4 100644 --- a/app/actions/AppActions.js +++ b/app/actions/AppActions.js @@ -1,24 +1,9 @@ -import AppDispatcher from '../dispatcher/AppDispatcher'; -import WebAPI from '../util/WebAPI'; +import alt from '../alt' -import { - ITEMS_GET_SUCCESS, - ITEMS_GET_ERROR -} from '../constants/AppConstants'; - -export default { - getItems() { - WebAPI.getItems() - .then((items) => { - AppDispatcher.dispatch({ - actionType: ITEMS_GET_SUCCESS, - items: items - }); - }) - .catch(() => { - AppDispatcher.dispatch({ - actionType: ITEMS_GET_ERROR - }); - }); +class AppActions { + constructor () { + this.generateActions('getItems') } -}; +} + +export default alt.createActions(AppActions) diff --git a/app/alt.js b/app/alt.js new file mode 100644 index 0000000..e54dc88 --- /dev/null +++ b/app/alt.js @@ -0,0 +1,2 @@ +import Alt from 'alt' +export default new Alt() diff --git a/app/app.jsx b/app/app.jsx index b637681..65958f3 100644 --- a/app/app.jsx +++ b/app/app.jsx @@ -1,13 +1,14 @@ -import './favicon.ico'; -import './index.html'; -import 'babel-core/polyfill'; -import 'normalize.css/normalize.css'; -import './scss/app.scss'; +import './favicon.ico' +import './index.html' +import 'babel-core/polyfill' +import 'normalize.css/normalize.css' +import './scss/app.scss' -import React from 'react'; -import App from './components/App/App'; +import React from 'react' +import Router from 'react-router' -React.render( - , - document.getElementById('app') -); +import routes from './routes' + +Router.run(routes, function (Handler) { + React.render(React.createElement(Handler), document.body) +}) diff --git a/app/app.tests.js b/app/app.tests.js index 4eb1763..63395c9 100644 --- a/app/app.tests.js +++ b/app/app.tests.js @@ -1,4 +1,4 @@ -import 'babel-core/polyfill'; +import 'babel-core/polyfill' -let context = require.context('.', true, /-test\.jsx?$/); -context.keys().forEach(context); +let context = require.context('.', true, /-test\.jsx?$/) +context.keys().forEach(context) diff --git a/app/components/App/App.jsx b/app/components/App/App.jsx index 9435cdf..e5df830 100644 --- a/app/components/App/App.jsx +++ b/app/components/App/App.jsx @@ -1,40 +1,27 @@ -import styles from './_App.scss'; +import styles from './_App.scss' -import React from 'react'; -import AppActions from '../../actions/AppActions'; -import ItemsStore from '../../stores/ItemsStore'; -import Body from '../Body/Body'; -import Footer from '../Footer/Footer'; - -function getAppState() { - return { - items: ItemsStore.getAll() - }; -} +import React from 'react' +import { RouteHandler } from 'react-router' +import AppActions from '../../actions/AppActions' +import ItemsStore from '../../stores/ItemsStore' +import Footer from '../Footer/Footer' export default class App extends React.Component { - - state = getAppState() - - componentDidMount() { - ItemsStore.addChangeListener(this.onChange); - AppActions.getItems(); - } - - componentWillUnmount() { - ItemsStore.removeChangeListener(this.onChange); - } - - onChange = () => { - this.setState(getAppState()); + constructor () { + super() + this.state = {items: []} + AppActions.getItems() + ItemsStore.listen((data) => { + this.setState({items: data.items}) + }) } - render() { + render () { return (
    - +
    - ); + ) } } diff --git a/app/components/Body/Body.jsx b/app/components/Body/Body.jsx index c2a3781..e4dfb0a 100644 --- a/app/components/Body/Body.jsx +++ b/app/components/Body/Body.jsx @@ -1,24 +1,25 @@ -import styles from './_Body.scss'; -import React from 'react'; -import Menu from '../Menu/Menu'; +import styles from './_Body.scss' -let { PropTypes } = React; +import React from 'react' +import Menu from '../Menu/Menu' + +let { PropTypes } = React export default class Body extends React.Component { static defaultProps = { items: [] - }; + } static propTypes = { items: PropTypes.array.isRequired - }; + } - render() { + render () { return (

    React Seed

    -

    This is an example seed app, powered by React, ES6 & webpack.

    +

    This is an example seed app, powered by React, ES6, Alt & webpack.

    Here is some example data:

    Getting started

    @@ -29,6 +30,6 @@ export default class Body extends React.Component {
  • Change the data rendered above. Look in:
    ./app/components/App/App.jsx
    Understand how data flows from the actions into the stores and then into the Body component.
  • - ); + ) } } diff --git a/app/components/Footer/Footer.jsx b/app/components/Footer/Footer.jsx index b470486..84f92fe 100644 --- a/app/components/Footer/Footer.jsx +++ b/app/components/Footer/Footer.jsx @@ -1,13 +1,13 @@ -import styles from './_Footer.scss'; -import React from 'react'; +import styles from './_Footer.scss' +import React from 'react' export default class Footer extends React.Component { - render() { - var year = (new Date()).getFullYear(); + render () { + var year = (new Date()).getFullYear() return (
    © Your Company {year}
    - ); + ) } } diff --git a/app/components/Footer/__tests__/Footer-test.jsx b/app/components/Footer/__tests__/Footer-test.jsx index 8ab4f1b..cfbd313 100644 --- a/app/components/Footer/__tests__/Footer-test.jsx +++ b/app/components/Footer/__tests__/Footer-test.jsx @@ -1,15 +1,15 @@ -import React from 'react/addons'; -import Footer from '../Footer.jsx'; -import { expect } from 'chai'; +import React from 'react/addons' +import Footer from '../Footer.jsx' +import { expect } from 'chai' -let { TestUtils } = React.addons; +let { TestUtils } = React.addons describe('Footer', () => { it('Should have the correct footer element', () => { let footer = TestUtils.renderIntoDocument(