);
8 | expect(renderedComponent.type()).toEqual('div');
9 | });
10 |
--------------------------------------------------------------------------------
/app/components/ProgressBar/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | it('should render an
tag', () => {
7 | const renderedComponent = shallow(
);
8 | expect(renderedComponent.type()).toEqual('div');
9 | });
10 |
--------------------------------------------------------------------------------
/app/containers/RepoListItem/IssueLink.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import A from 'components/A';
4 |
5 | const IssueLink = styled(A)`
6 | height: 100%;
7 | color: black;
8 | display: flex;
9 | align-items: center;
10 | justify-content: center;
11 | `;
12 |
13 | export default IssueLink;
14 |
--------------------------------------------------------------------------------
/internals/generators/container/sagas.js.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select } from 'redux-saga/effects';
2 |
3 | // Individual exports for testing
4 | export function* defaultSaga() {
5 | // See example in containers/HomePage/sagas.js
6 | }
7 |
8 | // All sagas to be loaded
9 | export default [
10 | defaultSaga,
11 | ];
12 |
--------------------------------------------------------------------------------
/internals/templates/containers/LanguageProvider/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider actions
4 | *
5 | */
6 |
7 | import {
8 | CHANGE_LOCALE,
9 | } from './constants';
10 |
11 | export function changeLocale(languageLocale) {
12 | return {
13 | type: CHANGE_LOCALE,
14 | locale: languageLocale,
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/internals/generators/component/test.js.hbs:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | // import { shallow } from 'enzyme';
3 |
4 | // import {{ properCase name }} from '../index';
5 |
6 | describe('<{{ properCase name }} />', () => {
7 | it('Expect to have unit tests specified', () => {
8 | expect(true).toEqual(false);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/internals/generators/container/test.js.hbs:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | // import { shallow } from 'enzyme';
3 |
4 | // import { {{ properCase name }} } from '../index';
5 |
6 | describe('<{{ properCase name }} />', () => {
7 | it('Expect to have unit tests specified', () => {
8 | expect(true).toEqual(false);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/internals/generators/container/reducer.test.js.hbs:
--------------------------------------------------------------------------------
1 |
2 | import { fromJS } from 'immutable';
3 | import {{ camelCase name }}Reducer from '../reducer';
4 |
5 | describe('{{ camelCase name }}Reducer', () => {
6 | it('returns the initial state', () => {
7 | expect({{ camelCase name }}Reducer(undefined, {})).toEqual(fromJS({}));
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/app/components/ListItem/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Wrapper = styled.li`
4 | width: 100%;
5 | height: 3em;
6 | display: flex;
7 | align-items: center;
8 | position: relative;
9 | border-top: 1px solid #eee;
10 |
11 | &:first-child {
12 | border-top: none;
13 | }
14 | `;
15 |
16 | export default Wrapper;
17 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * NotFoundPage Messages
3 | *
4 | * This contains all the text for the NotFoundPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'boilerplate.containers.NotFoundPage.header',
11 | defaultMessage: 'Page not found.',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/internals/templates/containers/HomePage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomePage Messages
3 | *
4 | * This contains all the text for the HomePage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.HomePage.header',
11 | defaultMessage: 'This is HomePage component!',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/components/Header/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Header from '../index';
5 |
6 | describe('
', () => {
7 | it('should render a div', () => {
8 | const renderedComponent = shallow(
9 |
10 | );
11 | expect(renderedComponent.find('div').length).toEqual(1);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/HomePage/selectors.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Homepage selectors
3 | */
4 |
5 | import { createSelector } from 'reselect';
6 |
7 | const selectHome = (state) => state.get('home');
8 |
9 | const makeSelectUsername = () => createSelector(
10 | selectHome,
11 | (homeState) => homeState.get('username')
12 | );
13 |
14 | export {
15 | selectHome,
16 | makeSelectUsername,
17 | };
18 |
--------------------------------------------------------------------------------
/app/components/IssueIcon/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import IssueIcon from '../index';
5 |
6 | describe('
', () => {
7 | it('should render a SVG', () => {
8 | const renderedComponent = shallow(
9 |
10 | );
11 | expect(renderedComponent.find('svg').length).toBe(1);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/internals/templates/containers/NotFoundPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * NotFoundPage Messages
3 | *
4 | * This contains all the text for the NotFoundPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.NotFoundPage.header',
11 | defaultMessage: 'This is NotFoundPage component!',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/components/ListItem/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Item from './Item';
4 | import Wrapper from './Wrapper';
5 |
6 | function ListItem(props) {
7 | return (
8 |
9 | -
10 | {props.item}
11 |
12 |
13 | );
14 | }
15 |
16 | ListItem.propTypes = {
17 | item: React.PropTypes.any,
18 | };
19 |
20 | export default ListItem;
21 |
--------------------------------------------------------------------------------
/app/components/ProgressBar/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.div`
4 | position: fixed;
5 | top: 0;
6 | left: 0;
7 | width: 100%;
8 | visibility: ${(props) => props.hidden ? 'hidden' : 'visible'};
9 | opacity: ${(props) => props.hidden ? '0' : '1'};
10 | transition: all 500ms ease-in-out;
11 | z-index: ${(props) => props.hidden ? '-10' : '9999'};
12 | `;
13 |
--------------------------------------------------------------------------------
/internals/generators/container/selectors.test.js.hbs:
--------------------------------------------------------------------------------
1 | // import { fromJS } from 'immutable';
2 | // import { makeSelect{{ properCase name }}Domain } from '../selectors';
3 |
4 | // const selector = makeSelect{{ properCase name}}Domain();
5 |
6 | describe('makeSelect{{ properCase name }}Domain', () => {
7 | it('Expect to have unit tests specified', () => {
8 | expect(true).toEqual(false);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/app/components/LoadingIndicator/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'enzyme';
3 |
4 | import LoadingIndicator from '../index';
5 |
6 | describe('
', () => {
7 | it('should render 13 divs', () => {
8 | const renderedComponent = render(
9 |
10 | );
11 | expect(renderedComponent.find('div').length).toBe(13);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/internals/generators/container/messages.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | * {{properCase name }} Messages
3 | *
4 | * This contains all the text for the {{properCase name }} component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.{{properCase name }}.header',
11 | defaultMessage: 'This is {{properCase name}} container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/internals/generators/component/messages.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | * {{ properCase name }} Messages
3 | *
4 | * This contains all the text for the {{ properCase name }} component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.{{ properCase name }}.header',
11 | defaultMessage: 'This is the {{ properCase name}} component !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/internals/generators/container/sagas.test.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | * Test sagas
3 | */
4 |
5 | /* eslint-disable redux-saga/yield-effects */
6 | // import { take, call, put, select } from 'redux-saga/effects';
7 | // import { defaultSaga } from '../sagas';
8 |
9 | // const generator = defaultSaga();
10 |
11 | describe('defaultSaga Saga', () => {
12 | it('Expect to have unit tests specified', () => {
13 | expect(true).toEqual(false);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/docs/testing/remote-testing.md:
--------------------------------------------------------------------------------
1 | # Remote testing
2 |
3 | ```Shell
4 | npm run start:tunnel
5 | ```
6 |
7 | This command will start a server and tunnel it with `ngrok`. You'll get a URL
8 | that looks a bit like this: `http://abcdef.ngrok.com`
9 |
10 | This URL will show the version of your application that's in the `build` folder,
11 | and it's accessible from the entire world! This is great for testing on different
12 | devices and from different locations!
13 |
--------------------------------------------------------------------------------
/app/components/Header/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomePage Messages
3 | *
4 | * This contains all the text for the HomePage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | home: {
10 | id: 'boilerplate.components.Header.home',
11 | defaultMessage: 'Home',
12 | },
13 | features: {
14 | id: 'boilerplate.components.Header.features',
15 | defaultMessage: 'Features',
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/tests/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 |
3 | import {
4 | selectLanguage,
5 | } from '../selectors';
6 |
7 | describe('selectLanguage', () => {
8 | it('should select the global state', () => {
9 | const globalState = fromJS({});
10 | const mockedState = fromJS({
11 | language: globalState,
12 | });
13 | expect(selectLanguage(mockedState)).toEqual(globalState);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * LocaleToggle Messages
3 | *
4 | * This contains all the text for the LanguageToggle component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | en: {
10 | id: 'boilerplate.containers.LocaleToggle.en',
11 | defaultMessage: 'en',
12 | },
13 | de: {
14 | id: 'boilerplate.containers.LocaleToggle.de',
15 | defaultMessage: 'de',
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import App from '../index';
5 |
6 | describe('
', () => {
7 | it('should render its children', () => {
8 | const children = (
Test );
9 | const renderedComponent = shallow(
10 |
11 | {children}
12 |
13 | );
14 | expect(renderedComponent.contains(children)).toBe(true);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/internals/generators/container/actions.test.js.hbs:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | defaultAction,
4 | } from '../actions';
5 | import {
6 | DEFAULT_ACTION,
7 | } from '../constants';
8 |
9 | describe('{{ properCase name }} actions', () => {
10 | describe('Default Action', () => {
11 | it('has a type of DEFAULT_ACTION', () => {
12 | const expected = {
13 | type: DEFAULT_ACTION,
14 | };
15 | expect(defaultAction()).toEqual(expected);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the languageToggle state domain
5 | */
6 | const selectLanguage = (state) => state.get('language');
7 |
8 | /**
9 | * Select the language locale
10 | */
11 |
12 | const makeSelectLocale = () => createSelector(
13 | selectLanguage,
14 | (languageState) => languageState.get('locale')
15 | );
16 |
17 | export {
18 | selectLanguage,
19 | makeSelectLocale,
20 | };
21 |
--------------------------------------------------------------------------------
/internals/templates/containers/LanguageProvider/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the languageToggle state domain
5 | */
6 | const selectLanguage = (state) => state.get('language');
7 |
8 | /**
9 | * Select the language locale
10 | */
11 |
12 | const makeSelectLocale = () => createSelector(
13 | selectLanguage,
14 | (languageState) => languageState.get('locale')
15 | );
16 |
17 | export {
18 | selectLanguage,
19 | makeSelectLocale,
20 | };
21 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/tests/actions.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | changeLocale,
3 | } from '../actions';
4 |
5 | import {
6 | CHANGE_LOCALE,
7 | } from '../constants';
8 |
9 | describe('LanguageProvider actions', () => {
10 | describe('Change Local Action', () => {
11 | it('has a type of CHANGE_LOCALE', () => {
12 | const expected = {
13 | type: CHANGE_LOCALE,
14 | locale: 'de',
15 | };
16 | expect(changeLocale('de')).toEqual(expected);
17 | });
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/docs/css/README.md:
--------------------------------------------------------------------------------
1 | # CSS
2 |
3 | This boilerplate uses [`styled-components`](https://github.com/styled-components/styled-components)
4 | allowing you to write your CSS in your JavaScript,
5 | removing the mapping between styles and components.
6 |
7 | `styled-components` let's us embrace component encapsulation while sanitize.css gives us
8 | data-driven cross-browser normalisation.
9 |
10 | Learn more:
11 |
12 | - [`styled-components`](styled-components.md)
13 | - [sanitize.css](sanitize.md)
14 | - [Using Sass](sass.md)
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - 7
5 | - 6
6 | - 5
7 |
8 | script:
9 | - node ./internals/scripts/generate-templates-for-linting
10 | - npm run test
11 | - npm run build
12 |
13 | before_install:
14 | - export CHROME_BIN=chromium-browser
15 | - export DISPLAY=:99.0
16 | - sh -e /etc/init.d/xvfb start
17 |
18 | notifications:
19 | email:
20 | on_failure: change
21 |
22 | after_success: 'npm run coveralls'
23 |
24 | cache:
25 | yarn: true
26 | directories:
27 | - node_modules
28 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NotFoundPage
3 | *
4 | * This is the page we show when the user visits a url that doesn't have a route
5 | */
6 |
7 | import React from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 |
10 | import H1 from 'components/H1';
11 | import messages from './messages';
12 |
13 | export default function NotFound() {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/internals/generators/container/reducer.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 | import {
9 | DEFAULT_ACTION,
10 | } from './constants';
11 |
12 | const initialState = fromJS({});
13 |
14 | function {{ camelCase name }}Reducer(state = initialState, action) {
15 | switch (action.type) {
16 | case DEFAULT_ACTION:
17 | return state;
18 | default:
19 | return state;
20 | }
21 | }
22 |
23 | export default {{ camelCase name }}Reducer;
24 |
--------------------------------------------------------------------------------
/app/components/ToggleOption/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * ToggleOption
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { injectIntl, intlShape } from 'react-intl';
9 |
10 | const ToggleOption = ({ value, message, intl }) => (
11 |
12 | {message ? intl.formatMessage(message) : value}
13 |
14 | );
15 |
16 | ToggleOption.propTypes = {
17 | value: React.PropTypes.string.isRequired,
18 | message: React.PropTypes.object,
19 | intl: intlShape.isRequired,
20 | };
21 |
22 | export default injectIntl(ToggleOption);
23 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | * AppConstants
3 | * Each action has a corresponding type, which the reducer knows and picks up on.
4 | * To avoid weird typos between the reducer and the actions, we save them as
5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid
6 | * reducers accidentally picking up actions they shouldn't.
7 | *
8 | * Follow this format:
9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT';
10 | */
11 |
12 | export const DEFAULT_LOCALE = 'en';
13 |
--------------------------------------------------------------------------------
/app/components/IssueIcon/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function IssueIcon(props) {
4 | return (
5 |
10 |
11 |
12 | );
13 | }
14 |
15 | IssueIcon.propTypes = {
16 | className: React.PropTypes.string,
17 | };
18 |
19 | export default IssueIcon;
20 |
--------------------------------------------------------------------------------
/internals/templates/containers/HomePage/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import { shallow } from 'enzyme';
4 |
5 | import HomePage from '../index';
6 | import messages from '../messages';
7 |
8 | describe('
', () => {
9 | it('should render the page message', () => {
10 | const renderedComponent = shallow(
11 |
12 | );
13 | expect(renderedComponent.contains(
14 |
15 | )).toEqual(true);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/app/components/Footer/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Footer Messages
3 | *
4 | * This contains all the text for the Footer component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | licenseMessage: {
10 | id: 'boilerplate.components.Footer.license.message',
11 | defaultMessage: 'This project is licensed under the MIT license.',
12 | },
13 | authorMessage: {
14 | id: 'boilerplate.components.Footer.author.message',
15 | defaultMessage: `
16 | Made with love by {author}.
17 | `,
18 | },
19 | });
20 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/actions.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | CHANGE_USERNAME,
3 | } from '../constants';
4 |
5 | import {
6 | changeUsername,
7 | } from '../actions';
8 |
9 | describe('Home Actions', () => {
10 | describe('changeUsername', () => {
11 | it('should return the correct type and the passed name', () => {
12 | const fixture = 'Max';
13 | const expectedResult = {
14 | type: CHANGE_USERNAME,
15 | name: fixture,
16 | };
17 |
18 | expect(changeUsername(fixture)).toEqual(expectedResult);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/app/containers/HomePage/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomeConstants
3 | * Each action has a corresponding type, which the reducer knows and picks up on.
4 | * To avoid weird typos between the reducer and the actions, we save them as
5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid
6 | * reducers accidentally picking up actions they shouldn't.
7 | *
8 | * Follow this format:
9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT';
10 | */
11 |
12 | export const CHANGE_USERNAME = 'boilerplate/Home/CHANGE_USERNAME';
13 |
--------------------------------------------------------------------------------
/internals/templates/containers/NotFoundPage/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import { shallow } from 'enzyme';
4 |
5 | import NotFoundPage from '../index';
6 | import messages from '../messages';
7 |
8 | describe('
', () => {
9 | it('should render the page message', () => {
10 | const renderedComponent = shallow(
11 |
12 | );
13 | expect(renderedComponent.contains(
14 |
15 | )).toEqual(true);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/selectors.js:
--------------------------------------------------------------------------------
1 | // makeSelectLocationState expects a plain JS object for the routing state
2 | const makeSelectLocationState = () => {
3 | let prevRoutingState;
4 | let prevRoutingStateJS;
5 |
6 | return (state) => {
7 | const routingState = state.get('route'); // or state.route
8 |
9 | if (!routingState.equals(prevRoutingState)) {
10 | prevRoutingState = routingState;
11 | prevRoutingStateJS = routingState.toJS();
12 | }
13 |
14 | return prevRoutingStateJS;
15 | };
16 | };
17 |
18 | export {
19 | makeSelectLocationState,
20 | };
21 |
--------------------------------------------------------------------------------
/app/containers/FeaturePage/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import { FormattedMessage } from 'react-intl';
4 |
5 | import H1 from 'components/H1';
6 | import messages from '../messages';
7 | import FeaturePage from '../index';
8 |
9 | describe('
', () => {
10 | it('should render its heading', () => {
11 | const renderedComponent = shallow(
12 |
13 | );
14 | expect(renderedComponent.contains(
15 |
16 |
17 |
18 | )).toBe(true);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/internals/generators/component/stateless.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 |
10 | {{#if wantMessages}}
11 | import { FormattedMessage } from 'react-intl';
12 | import messages from './messages';
13 | {{/if}}
14 |
15 | function {{ properCase name }}() {
16 | return (
17 |
18 | {{#if wantMessages}}
19 |
20 | {{/if}}
21 |
22 | );
23 | }
24 |
25 | {{ properCase name }}.propTypes = {
26 |
27 | };
28 |
29 | export default {{ properCase name }};
30 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/tests/reducer.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 |
3 | import languageProviderReducer from '../reducer';
4 | import {
5 | CHANGE_LOCALE,
6 | } from '../constants';
7 |
8 | describe('languageProviderReducer', () => {
9 | it('returns the initial state', () => {
10 | expect(languageProviderReducer(undefined, {})).toEqual(fromJS({
11 | locale: 'en',
12 | }));
13 | });
14 |
15 | it('changes the locale', () => {
16 | expect(languageProviderReducer(undefined, { type: CHANGE_LOCALE, locale: 'de' }).toJS()).toEqual({
17 | locale: 'de',
18 | });
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/internals/generators/utils/componentExists.js:
--------------------------------------------------------------------------------
1 | /**
2 | * componentExists
3 | *
4 | * Check whether the given component exist in either the components or containers directory
5 | */
6 |
7 | const fs = require('fs');
8 | const path = require('path');
9 | const pageComponents = fs.readdirSync(path.join(__dirname, '../../../app/components'));
10 | const pageContainers = fs.readdirSync(path.join(__dirname, '../../../app/containers'));
11 | const components = pageComponents.concat(pageContainers);
12 |
13 | function componentExists(comp) {
14 | return components.indexOf(comp) >= 0;
15 | }
16 |
17 | module.exports = componentExists;
18 |
--------------------------------------------------------------------------------
/docs/css/sanitize.md:
--------------------------------------------------------------------------------
1 | # `sanitize.css`
2 |
3 | Sanitize.css makes browsers render elements more in
4 | line with developer expectations (e.g. having the box model set to a cascading
5 | `box-sizing: border-box`) and preferences (its defaults can be individually
6 | overridden).
7 |
8 | It was selected over older projects like `normalize.css` and `reset.css` due
9 | to its greater flexibility and better alignment with CSSNext features like CSS
10 | variables.
11 |
12 | See the [official documentation](https://github.com/10up/sanitize.css) for more
13 | information.
14 |
15 | ---
16 |
17 | _Don't like this feature? [Click here](remove.md)_
18 |
--------------------------------------------------------------------------------
/app/components/H1/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import H1 from '../index';
5 |
6 | describe('
', () => {
7 | it('should render a prop', () => {
8 | const id = 'testId';
9 | const renderedComponent = shallow(
10 |
11 | );
12 | expect(renderedComponent.prop('id')).toEqual(id);
13 | });
14 |
15 | it('should render its text', () => {
16 | const children = 'Text';
17 | const renderedComponent = shallow(
18 |
{children}
19 | );
20 | expect(renderedComponent.contains(children)).toBe(true);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/app/components/H2/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import H2 from '../index';
5 |
6 | describe('
', () => {
7 | it('should render a prop', () => {
8 | const id = 'testId';
9 | const renderedComponent = shallow(
10 |
11 | );
12 | expect(renderedComponent.prop('id')).toEqual(id);
13 | });
14 |
15 | it('should render its text', () => {
16 | const children = 'Text';
17 | const renderedComponent = shallow(
18 |
{children}
19 | );
20 | expect(renderedComponent.contains(children)).toBe(true);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/app/components/H3/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import H3 from '../index';
5 |
6 | describe('
', () => {
7 | it('should render a prop', () => {
8 | const id = 'testId';
9 | const renderedComponent = shallow(
10 |
11 | );
12 | expect(renderedComponent.prop('id')).toEqual(id);
13 | });
14 |
15 | it('should render its text', () => {
16 | const children = 'Text';
17 | const renderedComponent = shallow(
18 |
{children}
19 | );
20 | expect(renderedComponent.contains(children)).toBe(true);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/app/components/Img/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Img.react.js
4 | *
5 | * Renders an image, enforcing the usage of the alt="" tag
6 | */
7 |
8 | import React, { PropTypes } from 'react';
9 |
10 | function Img(props) {
11 | return (
12 |
13 | );
14 | }
15 |
16 | // We require the use of src and alt, only enforced by react in dev mode
17 | Img.propTypes = {
18 | src: PropTypes.oneOfType([
19 | PropTypes.string,
20 | PropTypes.object,
21 | ]).isRequired,
22 | alt: PropTypes.string.isRequired,
23 | className: PropTypes.string,
24 | };
25 |
26 | export default Img;
27 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 |
9 | import {
10 | CHANGE_LOCALE,
11 | } from './constants';
12 | import {
13 | DEFAULT_LOCALE,
14 | } from '../App/constants';
15 |
16 | const initialState = fromJS({
17 | locale: DEFAULT_LOCALE,
18 | });
19 |
20 | function languageProviderReducer(state = initialState, action) {
21 | switch (action.type) {
22 | case CHANGE_LOCALE:
23 | return state
24 | .set('locale', action.locale);
25 | default:
26 | return state;
27 | }
28 | }
29 |
30 | export default languageProviderReducer;
31 |
--------------------------------------------------------------------------------
/app/components/ListItem/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 |
4 | import ListItem from '../index';
5 |
6 | describe('
', () => {
7 | it('should have a className', () => {
8 | const renderedComponent = mount(
);
9 | expect(renderedComponent.find('li').prop('className')).toBeDefined();
10 | });
11 |
12 | it('should render the content passed to it', () => {
13 | const content = (
Hello world!
);
14 | const renderedComponent = mount(
15 |
16 | );
17 | expect(renderedComponent.contains(content)).toBe(true);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/app/global-styles.js:
--------------------------------------------------------------------------------
1 | import { injectGlobal } from 'styled-components';
2 |
3 | /* eslint no-unused-expressions: 0 */
4 | injectGlobal`
5 | html,
6 | body {
7 | height: 100%;
8 | width: 100%;
9 | }
10 |
11 | body {
12 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
13 | }
14 |
15 | body.fontLoaded {
16 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
17 | }
18 |
19 | #app {
20 | background-color: #fafafa;
21 | min-height: 100%;
22 | min-width: 100%;
23 | }
24 |
25 | p,
26 | label {
27 | font-family: Georgia, Times, 'Times New Roman', serif;
28 | line-height: 1.5em;
29 | }
30 | `;
31 |
--------------------------------------------------------------------------------
/internals/generators/container/selectors.js.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the {{ camelCase name }} state domain
5 | */
6 | const select{{ properCase name }}Domain = () => (state) => state.get('{{ camelCase name }}');
7 |
8 | /**
9 | * Other specific selectors
10 | */
11 |
12 |
13 | /**
14 | * Default selector used by {{ properCase name }}
15 | */
16 |
17 | const makeSelect{{ properCase name }} = () => createSelector(
18 | select{{ properCase name }}Domain(),
19 | (substate) => substate.toJS()
20 | );
21 |
22 | export default makeSelect{{ properCase name }};
23 | export {
24 | select{{ properCase name }}Domain,
25 | };
26 |
--------------------------------------------------------------------------------
/app/components/Header/HeaderLink.js:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router';
2 | import styled from 'styled-components';
3 |
4 | export default styled(Link)`
5 | display: inline-flex;
6 | padding: 0.25em 2em;
7 | margin: 1em;
8 | text-decoration: none;
9 | border-radius: 4px;
10 | -webkit-font-smoothing: antialiased;
11 | -webkit-touch-callout: none;
12 | user-select: none;
13 | cursor: pointer;
14 | outline: 0;
15 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
16 | font-weight: bold;
17 | font-size: 16px;
18 | border: 2px solid #41ADDD;
19 | color: #41ADDD;
20 |
21 | &:active {
22 | background: #41ADDD;
23 | color: #FFF;
24 | }
25 | `;
26 |
--------------------------------------------------------------------------------
/internals/templates/global-styles.js:
--------------------------------------------------------------------------------
1 | import { injectGlobal } from 'styled-components';
2 |
3 | /* eslint no-unused-expressions: 0 */
4 | injectGlobal`
5 | html,
6 | body {
7 | height: 100%;
8 | width: 100%;
9 | }
10 |
11 | body {
12 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
13 | }
14 |
15 | body.fontLoaded {
16 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
17 | }
18 |
19 | #app {
20 | background-color: #fafafa;
21 | min-height: 100%;
22 | min-width: 100%;
23 | }
24 |
25 | p,
26 | label {
27 | font-family: Georgia, Times, 'Times New Roman', serif;
28 | line-height: 1.5em;
29 | }
30 | `;
31 |
--------------------------------------------------------------------------------
/app/components/Button/buttonStyles.js:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 |
3 | const buttonStyles = css`
4 | display: inline-block;
5 | box-sizing: border-box;
6 | padding: 0.25em 2em;
7 | text-decoration: none;
8 | border-radius: 4px;
9 | -webkit-font-smoothing: antialiased;
10 | -webkit-touch-callout: none;
11 | user-select: none;
12 | cursor: pointer;
13 | outline: 0;
14 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
15 | font-weight: bold;
16 | font-size: 16px;
17 | border: 2px solid #41addd;
18 | color: #41addd;
19 |
20 | &:active {
21 | background: #41addd;
22 | color: #fff;
23 | }
24 | `;
25 |
26 | export default buttonStyles;
27 |
--------------------------------------------------------------------------------
/internals/templates/containers/LanguageProvider/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 |
9 | import {
10 | CHANGE_LOCALE,
11 | } from './constants';
12 | import {
13 | DEFAULT_LOCALE,
14 | } from '../App/constants'; // eslint-disable-line
15 |
16 | const initialState = fromJS({
17 | locale: DEFAULT_LOCALE,
18 | });
19 |
20 | function languageProviderReducer(state = initialState, action) {
21 | switch (action.type) {
22 | case CHANGE_LOCALE:
23 | return state
24 | .set('locale', action.locale);
25 | default:
26 | return state;
27 | }
28 | }
29 |
30 | export default languageProviderReducer;
31 |
--------------------------------------------------------------------------------
/app/tests/store.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test store addons
3 | */
4 |
5 | import { browserHistory } from 'react-router';
6 | import configureStore from '../store';
7 |
8 | describe('configureStore', () => {
9 | let store;
10 |
11 | beforeAll(() => {
12 | store = configureStore({}, browserHistory);
13 | });
14 |
15 | describe('asyncReducers', () => {
16 | it('should contain an object for async reducers', () => {
17 | expect(typeof store.asyncReducers).toBe('object');
18 | });
19 | });
20 |
21 | describe('runSaga', () => {
22 | it('should contain a hook for `sagaMiddleware.run`', () => {
23 | expect(typeof store.runSaga).toBe('function');
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/internals/templates/tests/store.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test store addons
3 | */
4 |
5 | import { browserHistory } from 'react-router';
6 | import configureStore from '../store';
7 |
8 | describe('configureStore', () => {
9 | let store;
10 |
11 | beforeAll(() => {
12 | store = configureStore({}, browserHistory);
13 | });
14 |
15 | describe('asyncReducers', () => {
16 | it('should contain an object for async reducers', () => {
17 | expect(typeof store.asyncReducers).toEqual('object');
18 | });
19 | });
20 |
21 | describe('runSaga', () => {
22 | it('should contain a hook for `sagaMiddleware.run`', () => {
23 | expect(typeof store.runSaga).toEqual('function');
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/internals/generators/component/es6.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 |
10 | {{#if wantMessages}}
11 | import { FormattedMessage } from 'react-intl';
12 | import messages from './messages';
13 | {{/if}}
14 |
15 | class {{ properCase name }} extends React.Component { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | return (
18 |
19 | {{#if wantMessages}}
20 |
21 | {{/if}}
22 |
23 | );
24 | }
25 | }
26 |
27 | {{ properCase name }}.propTypes = {
28 |
29 | };
30 |
31 | export default {{ properCase name }};
32 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/tests/index.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Testing the NotFoundPage
3 | */
4 |
5 | import React from 'react';
6 | import { shallow } from 'enzyme';
7 | import { FormattedMessage } from 'react-intl';
8 |
9 | import H1 from 'components/H1';
10 | import NotFound from '../index';
11 |
12 | describe('
', () => {
13 | it('should render the Page Not Found text', () => {
14 | const renderedComponent = shallow(
15 |
16 | );
17 | expect(renderedComponent.contains(
18 |
19 |
23 | )).toEqual(true);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/internals/generators/component/es6.pure.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 |
10 | {{#if wantMessages}}
11 | import { FormattedMessage } from 'react-intl';
12 | import messages from './messages';
13 | {{/if}}
14 |
15 | class {{ properCase name }} extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | return (
18 |
19 | {{#if wantMessages}}
20 |
21 | {{/if}}
22 |
23 | );
24 | }
25 | }
26 |
27 | {{ properCase name }}.propTypes = {
28 |
29 | };
30 |
31 | export default {{ properCase name }};
32 |
--------------------------------------------------------------------------------
/app/containers/App/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | * AppConstants
3 | * Each action has a corresponding type, which the reducer knows and picks up on.
4 | * To avoid weird typos between the reducer and the actions, we save them as
5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid
6 | * reducers accidentally picking up actions they shouldn't.
7 | *
8 | * Follow this format:
9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT';
10 | */
11 |
12 | export const LOAD_REPOS = 'boilerplate/App/LOAD_REPOS';
13 | export const LOAD_REPOS_SUCCESS = 'boilerplate/App/LOAD_REPOS_SUCCESS';
14 | export const LOAD_REPOS_ERROR = 'boilerplate/App/LOAD_REPOS_ERROR';
15 | export const DEFAULT_LOCALE = 'en';
16 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/progress.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const readline = require('readline');
4 |
5 | /**
6 | * Adds an animated progress indicator
7 | *
8 | * @param {string} message The message to write next to the indicator
9 | * @param {number} amountOfDots The amount of dots you want to animate
10 | */
11 | function animateProgress(message, amountOfDots) {
12 | if (typeof amountOfDots !== 'number') {
13 | amountOfDots = 3;
14 | }
15 |
16 | let i = 0;
17 | return setInterval(function() {
18 | readline.cursorTo(process.stdout, 0);
19 | i = (i + 1) % (amountOfDots + 1);
20 | const dots = new Array(i + 1).join('.');
21 | process.stdout.write(message + dots);
22 | }, 500);
23 | }
24 |
25 | module.exports = animateProgress;
26 |
--------------------------------------------------------------------------------
/app/components/List/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'enzyme';
3 |
4 | import ListItem from 'components/ListItem';
5 | import List from '../index';
6 |
7 | describe('
', () => {
8 | it('should render the component if no items are passed', () => {
9 | const renderedComponent = render(
10 |
11 | );
12 | expect(renderedComponent.find(ListItem)).toBeDefined();
13 | });
14 |
15 | it('should render the items', () => {
16 | const items = [
17 | 'Hello',
18 | 'World',
19 | ];
20 | const renderedComponent = render(
21 |
22 | );
23 | expect(renderedComponent.find(items)).toBeDefined();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "React Boilerplate",
3 | "icons": [
4 | {
5 | "src": "favicon.png",
6 | "sizes": "48x48",
7 | "type": "image/png",
8 | "density": 1.0
9 | },
10 | {
11 | "src": "favicon.png",
12 | "sizes": "96x96",
13 | "type": "image/png",
14 | "density": 2.0
15 | },
16 | {
17 | "src": "favicon.png",
18 | "sizes": "144x144",
19 | "type": "image/png",
20 | "density": 3.0
21 | },
22 | {
23 | "src": "favicon.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "density": 4.0
27 | }
28 | ],
29 | "start_url": "index.html",
30 | "display": "standalone",
31 | "orientation": "portrait",
32 | "background_color": "#FFFFFF"
33 | }
--------------------------------------------------------------------------------
/app/components/LoadingIndicator/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Circle from './Circle';
4 | import Wrapper from './Wrapper';
5 |
6 | const LoadingIndicator = () => (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | export default LoadingIndicator;
24 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/reducer.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 |
3 | import homeReducer from '../reducer';
4 | import {
5 | changeUsername,
6 | } from '../actions';
7 |
8 | describe('homeReducer', () => {
9 | let state;
10 | beforeEach(() => {
11 | state = fromJS({
12 | username: '',
13 | });
14 | });
15 |
16 | it('should return the initial state', () => {
17 | const expectedResult = state;
18 | expect(homeReducer(undefined, {})).toEqual(expectedResult);
19 | });
20 |
21 | it('should handle the changeUsername action correctly', () => {
22 | const fixture = 'mxstbr';
23 | const expectedResult = state.set('username', fixture);
24 |
25 | expect(homeReducer(state, changeUsername(fixture))).toEqual(expectedResult);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/LoadingIndicator/tests/Circle.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 |
4 | import Circle from '../Circle';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = mount(
);
9 | expect(renderedComponent.find('div').length).toEqual(1);
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = mount(
);
14 | expect(renderedComponent.find('div').prop('className')).toBeDefined();
15 | });
16 |
17 | it('should not adopt attributes', () => {
18 | const id = 'test';
19 | const renderedComponent = mount(
);
20 | expect(renderedComponent.find('div').prop('id')).toBeUndefined();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/internals/templates/containers/HomePage/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomePage
3 | *
4 | * This is the first thing users see of our App, at the '/' route
5 | *
6 | * NOTE: while this component should technically be a stateless functional
7 | * component (SFC), hot reloading does not currently support SFCs. If hot
8 | * reloading is not a necessity for you then you can refactor it and remove
9 | * the linting exception.
10 | */
11 |
12 | import React from 'react';
13 | import { FormattedMessage } from 'react-intl';
14 | import messages from './messages';
15 |
16 | export default class HomePage extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
17 | render() {
18 | return (
19 |
20 |
21 |
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 |
4 | import A from 'components/A';
5 | import LocaleToggle from 'containers/LocaleToggle';
6 | import Wrapper from './Wrapper';
7 | import messages from './messages';
8 |
9 | function Footer() {
10 | return (
11 |
12 |
15 |
18 |
19 | Max Stoiber,
23 | }}
24 | />
25 |
26 |
27 | );
28 | }
29 |
30 | export default Footer;
31 |
--------------------------------------------------------------------------------
/app/components/List/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Ul from './Ul';
4 | import Wrapper from './Wrapper';
5 |
6 | function List(props) {
7 | const ComponentToRender = props.component;
8 | let content = (
);
9 |
10 | // If we have items, render them
11 | if (props.items) {
12 | content = props.items.map((item, index) => (
13 |
14 | ));
15 | } else {
16 | // Otherwise render a single component
17 | content = (
);
18 | }
19 |
20 | return (
21 |
22 |
25 |
26 | );
27 | }
28 |
29 | List.propTypes = {
30 | component: React.PropTypes.func.isRequired,
31 | items: React.PropTypes.array,
32 | };
33 |
34 | export default List;
35 |
--------------------------------------------------------------------------------
/internals/templates/containers/NotFoundPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NotFoundPage
3 | *
4 | * This is the page we show when the user visits a url that doesn't have a route
5 | *
6 | * NOTE: while this component should technically be a stateless functional
7 | * component (SFC), hot reloading does not currently support SFCs. If hot
8 | * reloading is not a necessity for you then you can refactor it and remove
9 | * the linting exception.
10 | */
11 |
12 | import React from 'react';
13 | import { FormattedMessage } from 'react-intl';
14 |
15 | import messages from './messages';
16 |
17 | export default class NotFound extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
18 | render() {
19 | return (
20 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 |
3 | import {
4 | selectHome,
5 | makeSelectUsername,
6 | } from '../selectors';
7 |
8 | describe('selectHome', () => {
9 | it('should select the home state', () => {
10 | const homeState = fromJS({
11 | userData: {},
12 | });
13 | const mockedState = fromJS({
14 | home: homeState,
15 | });
16 | expect(selectHome(mockedState)).toEqual(homeState);
17 | });
18 | });
19 |
20 | describe('makeSelectUsername', () => {
21 | const usernameSelector = makeSelectUsername();
22 | it('should select the username', () => {
23 | const username = 'mxstbr';
24 | const mockedState = fromJS({
25 | home: {
26 | username,
27 | },
28 | });
29 | expect(usernameSelector(mockedState)).toEqual(username);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/docs/general/gotchas.md:
--------------------------------------------------------------------------------
1 | # Gotchas
2 |
3 | These are some things to be aware of when using this boilerplate.
4 |
5 | ## Special images in HTML files
6 |
7 | If you specify your images in the `.html` files using the `
` tag, everything
8 | will work fine. The problem comes up if you try to include images using anything
9 | except that tag, like meta tags:
10 |
11 | ```HTML
12 |
13 | ```
14 |
15 | The webpack `html-loader` does not recognise this as an image file and will not
16 | transfer the image to the build folder. To get webpack to transfer them, you
17 | have to import them with the file loader in your JavaScript somewhere, e.g.:
18 |
19 | ```JavaScript
20 | import 'file?name=[name].[ext]!../img/yourimg.png';
21 | ```
22 |
23 | Then webpack will correctly transfer the image to the build folder.
24 |
--------------------------------------------------------------------------------
/app/components/Toggle/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * LocaleToggle
4 | *
5 | */
6 |
7 | import React from 'react';
8 |
9 | import Select from './Select';
10 | import ToggleOption from '../ToggleOption';
11 |
12 | function Toggle(props) {
13 | let content = (
-- );
14 |
15 | // If we have items, render them
16 | if (props.values) {
17 | content = props.values.map((value) => (
18 |
19 | ));
20 | }
21 |
22 | return (
23 |
24 | {content}
25 |
26 | );
27 | }
28 |
29 | Toggle.propTypes = {
30 | onToggle: React.PropTypes.func,
31 | values: React.PropTypes.array,
32 | value: React.PropTypes.string,
33 | messages: React.PropTypes.object,
34 | };
35 |
36 | export default Toggle;
37 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * App.react.js
4 | *
5 | * This component is the skeleton around the actual pages, and should only
6 | * contain code that should be seen on all pages. (e.g. navigation bar)
7 | *
8 | * NOTE: while this component should technically be a stateless functional
9 | * component (SFC), hot reloading does not currently support SFCs. If hot
10 | * reloading is not a necessity for you then you can refactor it and remove
11 | * the linting exception.
12 | */
13 |
14 | import React from 'react';
15 |
16 | export default class App extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
17 |
18 | static propTypes = {
19 | children: React.PropTypes.node,
20 | };
21 |
22 | render() {
23 | return (
24 |
25 | {React.Children.toArray(this.props.children)}
26 |
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/internals/generators/route/routeWithReducer.hbs:
--------------------------------------------------------------------------------
1 | {
2 | path: '{{ path }}',
3 | name: '{{ camelCase component }}',
4 | getComponent(nextState, cb) {
5 | const importModules = Promise.all([
6 | import('containers/{{ properCase component }}/reducer'),
7 | {{#if useSagas}}
8 | import('containers/{{ properCase component }}/sagas'),
9 | {{/if}}
10 | import('containers/{{ properCase component }}'),
11 | ]);
12 |
13 | const renderRoute = loadModule(cb);
14 |
15 | importModules.then(([reducer,{{#if useSagas}} sagas,{{/if}} component]) => {
16 | injectReducer('{{ camelCase component }}', reducer.default);
17 | {{#if useSagas}}
18 | injectSagas(sagas.default);
19 | {{/if}}
20 | renderRoute(component);
21 | });
22 |
23 | importModules.catch(errorLoading);
24 | },
25 | },$1
26 |
--------------------------------------------------------------------------------
/app/components/Button/tests/A.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import A from '../A';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow( );
9 | expect(renderedComponent.type()).toEqual('a');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/App/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Header from 'components/Header';
5 | import Footer from 'components/Footer';
6 | import { App } from '../index';
7 |
8 | describe('
', () => {
9 | it('should render the header', () => {
10 | const renderedComponent = shallow(
11 |
12 | );
13 | expect(renderedComponent.find(Header).length).toBe(1);
14 | });
15 |
16 | it('should render its children', () => {
17 | const children = (
Test );
18 | const renderedComponent = shallow(
19 |
20 | {children}
21 |
22 | );
23 | expect(renderedComponent.contains(children)).toBe(true);
24 | });
25 |
26 | it('should render the footer', () => {
27 | const renderedComponent = shallow(
28 |
29 | );
30 | expect(renderedComponent.find(Footer).length).toBe(1);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/app/containers/HomePage/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomeReducer
3 | *
4 | * The reducer takes care of our data. Using actions, we can change our
5 | * application state.
6 | * To add a new action, add it to the switch statement in the reducer function
7 | *
8 | * Example:
9 | * case YOUR_ACTION_CONSTANT:
10 | * return state.set('yourStateVariable', true);
11 | */
12 | import { fromJS } from 'immutable';
13 |
14 | import {
15 | CHANGE_USERNAME,
16 | } from './constants';
17 |
18 | // The initial state of the App
19 | const initialState = fromJS({
20 | username: '',
21 | });
22 |
23 | function homeReducer(state = initialState, action) {
24 | switch (action.type) {
25 | case CHANGE_USERNAME:
26 |
27 | // Delete prefixed '@' from the github username
28 | return state
29 | .set('username', action.name.replace(/@/gi, ''));
30 | default:
31 | return state;
32 | }
33 | }
34 |
35 | export default homeReducer;
36 |
--------------------------------------------------------------------------------
/docs/css/sass.md:
--------------------------------------------------------------------------------
1 | # Can I use Sass with this boilerplate?
2 |
3 | Yes, although we advise against it and **do not support this**. We selected
4 | [`styled-components`](https://github.com/styled-components/styled-components)
5 | over Sass because its approach is more powerful: instead of trying to
6 | give a styling language programmatic abilities, it pulls logic and configuration
7 | out into JS where we believe those features belong.
8 |
9 | If you _really_ still want (or need) to use Sass then...
10 |
11 | 1. You will need to add a [sass-loader](https://github.com/jtangelder/sass-loader)
12 | to the loaders section in `internals/webpack/webpack.base.babel.js` so it reads something like
13 | ```javascript
14 | {
15 | test: /\.scss$/,
16 | exclude: /node_modules/,
17 | loaders: ['style', 'css', 'sass']
18 | }
19 | ```
20 |
21 | Then run `npm i -D sass-loader node-sass`
22 |
23 | ...and you should be good to go!
24 |
--------------------------------------------------------------------------------
/internals/scripts/analyze.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const shelljs = require('shelljs');
4 | const animateProgress = require('./helpers/progress');
5 | const chalk = require('chalk');
6 | const addCheckMark = require('./helpers/checkmark');
7 |
8 | const progress = animateProgress('Generating stats');
9 |
10 | // Generate stats.json file with webpack
11 | shelljs.exec(
12 | 'webpack --config internals/webpack/webpack.prod.babel.js --profile --json > stats.json',
13 | addCheckMark.bind(null, callback) // Output a checkmark on completion
14 | );
15 |
16 | // Called after webpack has finished generating the stats.json file
17 | function callback() {
18 | clearInterval(progress);
19 | process.stdout.write(
20 | '\n\nOpen ' + chalk.magenta('http://webpack.github.io/analyse/') + ' in your browser and upload the stats.json file!' +
21 | chalk.blue('\n(Tip: ' + chalk.italic('CMD + double-click') + ' the link!)\n\n')
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/app/components/List/tests/Ul.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Ul from '../Ul';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow();
9 | expect(renderedComponent.type()).toEqual('ul');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow();
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow();
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow();
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/ReposList/index.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | import List from 'components/List';
4 | import ListItem from 'components/ListItem';
5 | import LoadingIndicator from 'components/LoadingIndicator';
6 | import RepoListItem from 'containers/RepoListItem';
7 |
8 | function ReposList({ loading, error, repos }) {
9 | if (loading) {
10 | return
;
11 | }
12 |
13 | if (error !== false) {
14 | const ErrorComponent = () => (
15 |
16 | );
17 | return
;
18 | }
19 |
20 | if (repos !== false) {
21 | return
;
22 | }
23 |
24 | return null;
25 | }
26 |
27 | ReposList.propTypes = {
28 | loading: PropTypes.bool,
29 | error: PropTypes.any,
30 | repos: PropTypes.any,
31 | };
32 |
33 | export default ReposList;
34 |
--------------------------------------------------------------------------------
/app/components/ListItem/tests/Item.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Item from '../Item';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('div');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/FeaturePage/tests/List.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import List from '../List';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('ul');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/Form.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Form from '../Form';
5 |
6 | describe(' ', () => {
7 | it('should render an
);
9 | expect(renderedComponent.type()).toEqual('form');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/Header/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 |
4 | import A from './A';
5 | import Img from './Img';
6 | import NavBar from './NavBar';
7 | import HeaderLink from './HeaderLink';
8 | import Banner from './banner.jpg';
9 | import messages from './messages';
10 |
11 | class Header extends React.Component { // eslint-disable-line react/prefer-stateless-function
12 | render() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 | }
30 |
31 | export default Header;
32 |
--------------------------------------------------------------------------------
/app/components/Header/tests/A.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount, render } from 'enzyme';
3 |
4 | import A from '../A';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = render( );
9 | expect(renderedComponent.find('a').length).toEqual(1);
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = mount(
);
14 | expect(renderedComponent.find('a').prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = mount(
);
20 | expect(renderedComponent.find('a').prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = mount(
);
25 | expect(renderedComponent.find('a').prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/Input.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Input from '../Input';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('input');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/Button/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('div');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/List/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('div');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/ListItem/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow( );
9 | expect(renderedComponent.type()).toEqual('li');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/Toggle/tests/Select.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Select from '../Select';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = shallow( );
9 | expect(renderedComponent.type()).toEqual('select');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/tests/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 |
3 | import { makeSelectLocationState } from 'containers/App/selectors';
4 |
5 | describe('makeSelectLocationState', () => {
6 | it('should select the route as a plain JS object', () => {
7 | const route = fromJS({
8 | locationBeforeTransitions: null,
9 | });
10 | const mockedState = fromJS({
11 | route,
12 | });
13 | expect(makeSelectLocationState()(mockedState)).toEqual(route.toJS());
14 | });
15 |
16 | it('should return cached js routeState for same concurrent calls', () => {
17 | const route = fromJS({
18 | locationBeforeTransitions: null,
19 | });
20 | const mockedState = fromJS({
21 | route,
22 | });
23 | const selectLocationState = makeSelectLocationState();
24 |
25 | const firstRouteStateJS = selectLocationState(mockedState);
26 | expect(selectLocationState(mockedState)).toBe(firstRouteStateJS);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/app/components/Footer/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = shallow( );
9 | expect(renderedComponent.type()).toEqual('footer');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('div');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/RepoListItem/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('div');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/LoadingIndicator/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('div');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/FeaturePage/tests/ListItem.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import ListItem from '../ListItem';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow( );
9 | expect(renderedComponent.type()).toEqual('li');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/AtPrefix.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import AtPrefix from '../AtPrefix';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = shallow( );
9 | expect(renderedComponent.type()).toEqual('span');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/Section.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Section from '../Section';
5 |
6 | describe('', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = shallow();
9 | expect(renderedComponent.type()).toEqual('section');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow();
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow();
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow();
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/tests/i18n.test.js:
--------------------------------------------------------------------------------
1 | import { DEFAULT_LOCALE } from '../containers/App/constants';
2 | import { formatTranslationMessages } from '../i18n';
3 |
4 | jest.mock('../translations/en.json', () => (
5 | {
6 | message1: 'default message',
7 | message2: 'default message 2',
8 | }
9 | ));
10 |
11 | const esTranslationMessages = {
12 | message1: 'mensaje predeterminado',
13 | message2: '',
14 | };
15 |
16 | describe('formatTranslationMessages', () => {
17 | it('should build only defaults when DEFAULT_LOCALE', () => {
18 | const result = formatTranslationMessages(DEFAULT_LOCALE, { a: 'a' });
19 |
20 | expect(result).toEqual({ a: 'a' });
21 | });
22 |
23 |
24 | it('should combine default locale and current locale when not DEFAULT_LOCALE', () => {
25 | const result = formatTranslationMessages('', esTranslationMessages);
26 |
27 | expect(result).toEqual({
28 | message1: 'mensaje predeterminado',
29 | message2: 'default message 2',
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/app/containers/RepoListItem/tests/IssueIcon.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow, render } from 'enzyme';
3 |
4 | import IssueIcon from '../IssueIcon';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = render( );
9 | expect(renderedComponent.find('svg').length).toEqual(1);
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should adopt any attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeDefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/RepoListItem/tests/RepoLink.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow, render } from 'enzyme';
3 |
4 | import RepoLink from '../RepoLink';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = render( );
9 | expect(renderedComponent.find('a').length).toEqual(1);
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/Toggle/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import { IntlProvider, defineMessages } from 'react-intl';
4 |
5 | import Toggle from '../index';
6 |
7 | describe(' ', () => {
8 | it('should contain default text', () => {
9 | const defaultEnMessage = 'someContent';
10 | const defaultDeMessage = 'someOtherContent';
11 | const messages = defineMessages({
12 | en: {
13 | id: 'boilerplate.containers.LocaleToggle.en',
14 | defaultMessage: defaultEnMessage,
15 | },
16 | de: {
17 | id: 'boilerplate.containers.LocaleToggle.en',
18 | defaultMessage: defaultDeMessage,
19 | },
20 | });
21 | const renderedComponent = shallow(
22 |
23 |
24 |
25 | );
26 | expect(renderedComponent.contains( )).toBe(true);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/app/containers/RepoListItem/tests/IssueLink.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow, render } from 'enzyme';
3 |
4 | import IssueLink from '../IssueLink';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = render( );
9 | expect(renderedComponent.find('a').length).toEqual(1);
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/components/Button/tests/StyledButton.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import StyledButton from '../StyledButton';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = shallow( );
9 | expect(renderedComponent.type()).toEqual('button');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/HomePage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Home Actions
3 | *
4 | * Actions change things in your application
5 | * Since this boilerplate uses a uni-directional data flow, specifically redux,
6 | * we have these actions which are the only way your application interacts with
7 | * your application state. This guarantees that your state is up to date and nobody
8 | * messes it up weirdly somewhere.
9 | *
10 | * To add a new Action:
11 | * 1) Import your constant
12 | * 2) Add a function like this:
13 | * export function yourAction(var) {
14 | * return { type: YOUR_ACTION_CONSTANT, var: var }
15 | * }
16 | */
17 |
18 | import {
19 | CHANGE_USERNAME,
20 | } from './constants';
21 |
22 | /**
23 | * Changes the input field of the form
24 | *
25 | * @param {name} name The new text of the input field
26 | *
27 | * @return {object} An action object with a type of CHANGE_USERNAME
28 | */
29 | export function changeUsername(name) {
30 | return {
31 | type: CHANGE_USERNAME,
32 | name,
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/app/containers/FeaturePage/tests/ListItemTitle.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import ListItemTitle from '../ListItemTitle';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = shallow( );
9 | expect(renderedComponent.type()).toEqual('p');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/internals/generators/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * generator/index.js
3 | *
4 | * Exports the generators so plop knows them
5 | */
6 |
7 | const fs = require('fs');
8 | const path = require('path');
9 | const componentGenerator = require('./component/index.js');
10 | const containerGenerator = require('./container/index.js');
11 | const routeGenerator = require('./route/index.js');
12 | const languageGenerator = require('./language/index.js');
13 |
14 | module.exports = (plop) => {
15 | plop.setGenerator('component', componentGenerator);
16 | plop.setGenerator('container', containerGenerator);
17 | plop.setGenerator('route', routeGenerator);
18 | plop.setGenerator('language', languageGenerator);
19 | plop.addHelper('directory', (comp) => {
20 | try {
21 | fs.accessSync(path.join(__dirname, `../../app/containers/${comp}`), fs.F_OK);
22 | return `containers/${comp}`;
23 | } catch (e) {
24 | return `components/${comp}`;
25 | }
26 | });
27 | plop.addHelper('curly', (object, open) => (open ? '{' : '}'));
28 | };
29 |
--------------------------------------------------------------------------------
/app/containers/HomePage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomePage Messages
3 | *
4 | * This contains all the text for the HomePage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | startProjectHeader: {
10 | id: 'boilerplate.containers.HomePage.start_project.header',
11 | defaultMessage: 'Start your next react project in seconds',
12 | },
13 | startProjectMessage: {
14 | id: 'boilerplate.containers.HomePage.start_project.message',
15 | defaultMessage: 'A highly scalable, offline-first foundation with the best DX and a focus on performance and best practices',
16 | },
17 | trymeHeader: {
18 | id: 'boilerplate.containers.HomePage.tryme.header',
19 | defaultMessage: 'Try me!',
20 | },
21 | trymeMessage: {
22 | id: 'boilerplate.containers.HomePage.tryme.message',
23 | defaultMessage: 'Show Github repositories by',
24 | },
25 | trymeAtPrefix: {
26 | id: 'boilerplate.containers.HomePage.tryme.atPrefix',
27 | defaultMessage: '@',
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/app/components/Footer/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import { FormattedMessage } from 'react-intl';
4 |
5 | import A from 'components/A';
6 | import messages from '../messages';
7 | import Footer from '../index';
8 |
9 | describe('', () => {
10 | it('should render the copyright notice', () => {
11 | const renderedComponent = shallow(
12 |
13 | );
14 | expect(renderedComponent.contains(
15 |
18 | )).toBe(true);
19 | });
20 |
21 | it('should render the credits', () => {
22 | const renderedComponent = shallow();
23 | expect(renderedComponent.contains(
24 |
,
29 | }}
30 | />
31 |
32 | )).toBe(true);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/CenteredSection.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import CenteredSection from '../CenteredSection';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = shallow( );
9 | expect(renderedComponent.type()).toEqual('section');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow( );
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow( );
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow( );
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Maximilian Stoiber
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/components/Header/tests/Img.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount, render } from 'enzyme';
3 |
4 | import Img from '../Img';
5 |
6 | describe(' ', () => {
7 | it('should render an tag', () => {
8 | const renderedComponent = render( );
9 | expect(renderedComponent.find('img').length).toEqual(1);
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = mount( );
14 | expect(renderedComponent.find('img').prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const renderedComponent = mount( );
19 | expect(renderedComponent.find('img').prop('alt')).toEqual('test');
20 | });
21 |
22 | it('should not adopt an invalid attribute', () => {
23 | const renderedComponent = mount( );
24 | expect(renderedComponent.find('img').prop('attribute')).toBeUndefined();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/internals/templates/i18n.js:
--------------------------------------------------------------------------------
1 | /**
2 | * i18n.js
3 | *
4 | * This will setup the i18n language files and locale data for your app.
5 | *
6 | */
7 | import { addLocaleData } from 'react-intl';
8 | import enLocaleData from 'react-intl/locale-data/en';
9 |
10 | import { DEFAULT_LOCALE } from './containers/App/constants'; // eslint-disable-line
11 | import enTranslationMessages from './translations/en.json';
12 |
13 | export const appLocales = [
14 | 'en',
15 | ];
16 |
17 | addLocaleData(enLocaleData);
18 |
19 | export const formatTranslationMessages = (locale, messages) => {
20 | const defaultFormattedMessages = locale !== DEFAULT_LOCALE
21 | ? formatTranslationMessages(DEFAULT_LOCALE, enTranslationMessages)
22 | : {};
23 | return Object.keys(messages).reduce((formattedMessages, key) => {
24 | let message = messages[key];
25 | if (!message && locale !== DEFAULT_LOCALE) {
26 | message = defaultFormattedMessages[key];
27 | }
28 | return Object.assign(formattedMessages, { [key]: message });
29 | }, {});
30 | };
31 |
32 | export const translationMessages = {
33 | en: formatTranslationMessages('en', enTranslationMessages),
34 | };
35 |
--------------------------------------------------------------------------------
/app/components/Button/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Button.react.js
4 | *
5 | * A common button, if you pass it a prop "route" it'll render a link to a react-router route
6 | * otherwise it'll render a link with an onclick
7 | */
8 |
9 | import React, { PropTypes, Children } from 'react';
10 |
11 | import A from './A';
12 | import StyledButton from './StyledButton';
13 | import Wrapper from './Wrapper';
14 |
15 | function Button(props) {
16 | // Render an anchor tag
17 | let button = (
18 |
19 | {Children.toArray(props.children)}
20 |
21 | );
22 |
23 | // If the Button has a handleRoute prop, we want to render a button
24 | if (props.handleRoute) {
25 | button = (
26 |
27 | {Children.toArray(props.children)}
28 |
29 | );
30 | }
31 |
32 | return (
33 |
34 | {button}
35 |
36 | );
37 | }
38 |
39 | Button.propTypes = {
40 | handleRoute: PropTypes.func,
41 | href: PropTypes.string,
42 | onClick: PropTypes.func,
43 | children: PropTypes.node.isRequired,
44 | };
45 |
46 | export default Button;
47 |
--------------------------------------------------------------------------------
/internals/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | React.js Boilerplate
12 |
13 |
14 |
15 | If you're seeing this message, that means JavaScript has been disabled on your browser , please enable JS to make this app work.
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/components/ToggleOption/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow, mount } from 'enzyme';
3 | import { IntlProvider, defineMessages } from 'react-intl';
4 |
5 | import ToggleOption from '../index';
6 |
7 | describe(' ', () => {
8 | it('should render default language messages', () => {
9 | const defaultEnMessage = 'someContent';
10 | const message = defineMessages({
11 | enMessage: {
12 | id: 'boilerplate.containers.LocaleToggle.en',
13 | defaultMessage: defaultEnMessage,
14 | },
15 | });
16 | const renderedComponent = shallow(
17 |
18 |
19 |
20 | );
21 | expect(renderedComponent.contains( )).toBe(true);
22 | });
23 |
24 | it('should display `value`(two letter language code) when `message` is absent', () => {
25 | const renderedComponent = mount(
26 |
27 |
28 |
29 | );
30 | expect(renderedComponent.text()).toBe('de');
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider
4 | *
5 | * this component connects the redux state language locale to the
6 | * IntlProvider component and i18n messages (loaded from `app/translations`)
7 | */
8 |
9 | import React from 'react';
10 | import { connect } from 'react-redux';
11 | import { createSelector } from 'reselect';
12 | import { IntlProvider } from 'react-intl';
13 |
14 | import { makeSelectLocale } from './selectors';
15 |
16 | export class LanguageProvider extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
17 | render() {
18 | return (
19 |
20 | {React.Children.only(this.props.children)}
21 |
22 | );
23 | }
24 | }
25 |
26 | LanguageProvider.propTypes = {
27 | locale: React.PropTypes.string,
28 | messages: React.PropTypes.object,
29 | children: React.PropTypes.element.isRequired,
30 | };
31 |
32 | const mapStateToProps = createSelector(
33 | makeSelectLocale(),
34 | (locale) => ({ locale })
35 | );
36 |
37 | export default connect(mapStateToProps)(LanguageProvider);
38 |
--------------------------------------------------------------------------------
/server/logger.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const chalk = require('chalk');
4 | const ip = require('ip');
5 |
6 | const divider = chalk.gray('\n-----------------------------------');
7 |
8 | /**
9 | * Logger middleware, you can customize it to make messages more personal
10 | */
11 | const logger = {
12 |
13 | // Called whenever there's an error on the server we want to print
14 | error: (err) => {
15 | console.error(chalk.red(err));
16 | },
17 |
18 | // Called when express.js app starts on given port w/o errors
19 | appStarted: (port, host, tunnelStarted) => {
20 | console.log(`Server started ! ${chalk.green('✓')}`);
21 |
22 | // If the tunnel started, log that and the URL it's available at
23 | if (tunnelStarted) {
24 | console.log(`Tunnel initialised ${chalk.green('✓')}`);
25 | }
26 |
27 | console.log(`
28 | ${chalk.bold('Access URLs:')}${divider}
29 | Localhost: ${chalk.magenta(`http://${host}:${port}`)}
30 | LAN: ${chalk.magenta(`http://${ip.address()}:${port}`) +
31 | (tunnelStarted ? `\n Proxy: ${chalk.magenta(tunnelStarted)}` : '')}${divider}
32 | ${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)}
33 | `);
34 | },
35 | };
36 |
37 | module.exports = logger;
38 |
--------------------------------------------------------------------------------
/docs/general/progressbar.md:
--------------------------------------------------------------------------------
1 | ## Progress Bar
2 | Route transitions are not smooth over the network because chunks generated by webpack take some time to load. It would cause a poor user experience especially for those with slow internet connections. To tackle this UX issue, `react-boilerplate`'s example app comes with a progress bar which shows _fake_ progress while browser downloads the chunk and renders it.
3 |
4 | **How it works**
5 |
6 | The ProgressBar percentage depends on the [`listenBefore()`](https://github.com/ReactTraining/react-router/blob/master/docs/API.md#router-1) hook that comes with react-router. A Route change will be caught by React's component life cycle methods.
7 |
8 | - Initially, the percentage is `-1`. The progress bar is hidden.
9 | - Progress is set to `0` when the location changes and it keeps on increasing until the new route is mounted or it reaches `99` _(whichever happens first)_.
10 | - Percentage is set to `100` when the new route mounts.
11 | - Percentage is reset to `-1` and ProgressBar is ready for a new route change.
12 |
13 | **Default behaviour**
14 | - ` ` will not show progress for previously loaded routes.
15 |
16 | **Limitations**
17 | - It will not show progress before `main.js` is loaded in the DOM.
18 |
--------------------------------------------------------------------------------
/app/utils/request.js:
--------------------------------------------------------------------------------
1 | import 'whatwg-fetch';
2 |
3 | /**
4 | * Parses the JSON returned by a network request
5 | *
6 | * @param {object} response A response from a network request
7 | *
8 | * @return {object} The parsed JSON from the request
9 | */
10 | function parseJSON(response) {
11 | return response.json();
12 | }
13 |
14 | /**
15 | * Checks if a network request came back fine, and throws an error if not
16 | *
17 | * @param {object} response A response from a network request
18 | *
19 | * @return {object|undefined} Returns either the response, or throws an error
20 | */
21 | function checkStatus(response) {
22 | if (response.status >= 200 && response.status < 300) {
23 | return response;
24 | }
25 |
26 | const error = new Error(response.statusText);
27 | error.response = response;
28 | throw error;
29 | }
30 |
31 | /**
32 | * Requests a URL, returning a promise
33 | *
34 | * @param {string} url The URL we want to request
35 | * @param {object} [options] The options we want to pass to "fetch"
36 | *
37 | * @return {object} The response data
38 | */
39 | export default function request(url, options) {
40 | return fetch(url, options)
41 | .then(checkStatus)
42 | .then(parseJSON);
43 | }
44 |
--------------------------------------------------------------------------------
/docs/testing/README.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | - [Unit Testing](unit-testing.md)
4 | - [Component Testing](component-testing.md)
5 | - [Remote Testing](remote-testing.md)
6 |
7 | Testing your application is a vital part of serious development. There are a few
8 | things you should test. If you've never done this before start with [unit testing](unit-testing.md).
9 | Move on to [component testing](component-testing.md) when you feel like you
10 | understand that!
11 |
12 | We also support [remote testing](remote-testing.md) your local application,
13 | which is quite awesome, so definitely check that out!
14 |
15 | ## Usage with this boilerplate
16 |
17 | To test your application started with this boilerplate do the following:
18 |
19 | 1. Sprinkle `.test.js` files directly next to the parts of your application you
20 | want to test. (Or in `test/` subdirectories, it doesn't really matter as long
21 | as they are directly next to those parts and end in `.test.js`)
22 |
23 | 1. Write your unit and component tests in those files.
24 |
25 | 1. Run `npm run test` in your terminal and see all the tests pass! (hopefully)
26 |
27 | There are a few more commands related to testing, checkout the [commands documentation](../general/commands.md#testing)
28 | for the full list!
29 |
--------------------------------------------------------------------------------
/app/containers/App/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * App
4 | *
5 | * This component is the skeleton around the actual pages, and should only
6 | * contain code that should be seen on all pages. (e.g. navigation bar)
7 | */
8 |
9 | import React from 'react';
10 | import Helmet from 'react-helmet';
11 | import styled from 'styled-components';
12 |
13 | import Header from 'components/Header';
14 | import Footer from 'components/Footer';
15 | import withProgressBar from 'components/ProgressBar';
16 |
17 | const AppWrapper = styled.div`
18 | max-width: calc(768px + 16px * 2);
19 | margin: 0 auto;
20 | display: flex;
21 | min-height: 100%;
22 | padding: 0 16px;
23 | flex-direction: column;
24 | `;
25 |
26 | export function App(props) {
27 | return (
28 |
29 |
36 |
37 | {React.Children.toArray(props.children)}
38 |
39 |
40 | );
41 | }
42 |
43 | App.propTypes = {
44 | children: React.PropTypes.node,
45 | };
46 |
47 | export default withProgressBar(App);
48 |
--------------------------------------------------------------------------------
/internals/webpack/webpack.dll.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WEBPACK DLL GENERATOR
3 | *
4 | * This profile is used to cache webpack's module
5 | * contexts for external library and framework type
6 | * dependencies which will usually not change often enough
7 | * to warrant building them from scratch every time we use
8 | * the webpack process.
9 | */
10 |
11 | const { join } = require('path');
12 | const defaults = require('lodash/defaultsDeep');
13 | const webpack = require('webpack');
14 | const pkg = require(join(process.cwd(), 'package.json'));
15 | const dllPlugin = require('../config').dllPlugin;
16 |
17 | if (!pkg.dllPlugin) { process.exit(0); }
18 |
19 | const dllConfig = defaults(pkg.dllPlugin, dllPlugin.defaults);
20 | const outputPath = join(process.cwd(), dllConfig.path);
21 |
22 | module.exports = require('./webpack.base.babel')({
23 | context: process.cwd(),
24 | entry: dllConfig.dlls ? dllConfig.dlls : dllPlugin.entry(pkg),
25 | devtool: 'eval',
26 | output: {
27 | filename: '[name].dll.js',
28 | path: outputPath,
29 | library: '[name]',
30 | },
31 | plugins: [
32 | new webpack.DllPlugin({ name: '[name]', path: join(outputPath, '[name].json') }), // eslint-disable-line no-new
33 | ],
34 | performance: {
35 | hints: false,
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/__snapshots__/sagas.test.js.snap:
--------------------------------------------------------------------------------
1 | exports[`getRepos Saga should call the repoLoadingError action if the response errors 1`] = `
2 | Object {
3 | "@@redux-saga/IO": true,
4 | "SELECT": Object {
5 | "args": Array [],
6 | "selector": [Function],
7 | },
8 | }
9 | `;
10 |
11 | exports[`getRepos Saga should call the repoLoadingError action if the response errors 2`] = `
12 | Object {
13 | "@@redux-saga/IO": true,
14 | "CALL": Object {
15 | "args": Array [
16 | "https://api.github.com/users/mxstbr/repos?type=all&sort=updated",
17 | ],
18 | "context": null,
19 | "fn": [Function],
20 | },
21 | }
22 | `;
23 |
24 | exports[`getRepos Saga should dispatch the reposLoaded action if it requests the data successfully 1`] = `
25 | Object {
26 | "@@redux-saga/IO": true,
27 | "SELECT": Object {
28 | "args": Array [],
29 | "selector": [Function],
30 | },
31 | }
32 | `;
33 |
34 | exports[`getRepos Saga should dispatch the reposLoaded action if it requests the data successfully 2`] = `
35 | Object {
36 | "@@redux-saga/IO": true,
37 | "CALL": Object {
38 | "args": Array [
39 | "https://api.github.com/users/mxstbr/repos?type=all&sort=updated",
40 | ],
41 | "context": null,
42 | "fn": [Function],
43 | },
44 | }
45 | `;
46 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # React Boilerplate
2 |
3 | Before opening a new issue, please take a moment to review our [**community guidelines**](https://github.com/react-boilerplate/react-boilerplate/blob/master/.github/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved.
4 |
5 | Please direct redux-saga related questions to stack overflow:
6 | http://stackoverflow.com/questions/tagged/redux-saga
7 |
8 | For questions related to the boilerplate itself, you can also find answers on our gitter chat:
9 | https://gitter.im/mxstbr/react-boilerplate
10 |
11 | **Before opening a new issue, you may find an answer in already closed issues**:
12 | https://github.com/react-boilerplate/react-boilerplate/issues?q=is%3Aissue+is%3Aclosed
13 |
14 | ## Issue Type
15 |
16 | - [ ] Bug (https://github.com/react-boilerplate/react-boilerplate/blob/master/.github/CONTRIBUTING.md#bug-reports)
17 | - [ ] Feature (https://github.com/react-boilerplate/react-boilerplate/blob/master/.github/CONTRIBUTING.md#feature-requests)
18 |
19 | ## Description
20 |
21 | (Add images if possible)
22 |
23 | ## Steps to reproduce
24 |
25 | (Add link to a demo on https://jsfiddle.net or similar if possible)
26 |
27 | # Versions
28 |
29 | - React-Boilerplate (see `package.json`):
30 | - Node/NPM:
31 | - Browser:
32 |
--------------------------------------------------------------------------------
/app/components/LoadingIndicator/Circle.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import styled, { keyframes } from 'styled-components';
3 |
4 | const circleFadeDelay = keyframes`
5 | 0%,
6 | 39%,
7 | 100% {
8 | opacity: 0;
9 | }
10 |
11 | 40% {
12 | opacity: 1;
13 | }
14 | `;
15 |
16 | const Circle = (props) => {
17 | const CirclePrimitive = styled.div`
18 | width: 100%;
19 | height: 100%;
20 | position: absolute;
21 | left: 0;
22 | top: 0;
23 | ${props.rotate && `
24 | -webkit-transform: rotate(${props.rotate}deg);
25 | -ms-transform: rotate(${props.rotate}deg);
26 | transform: rotate(${props.rotate}deg);
27 | `}
28 |
29 | &:before {
30 | content: '';
31 | display: block;
32 | margin: 0 auto;
33 | width: 15%;
34 | height: 15%;
35 | background-color: #999;
36 | border-radius: 100%;
37 | animation: ${circleFadeDelay} 1.2s infinite ease-in-out both;
38 | ${props.delay && `
39 | -webkit-animation-delay: ${props.delay}s;
40 | animation-delay: ${props.delay}s;
41 | `}
42 | }
43 | `;
44 | return ;
45 | };
46 |
47 | Circle.propTypes = {
48 | delay: PropTypes.number,
49 | rotate: PropTypes.number,
50 | };
51 |
52 | export default Circle;
53 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | React.js Boilerplate
12 |
13 |
14 |
15 | If you're seeing this message, that means JavaScript has been disabled on your browser , please enable JS to make this app work.
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/containers/App/selectors.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The global state selectors
3 | */
4 |
5 | import { createSelector } from 'reselect';
6 |
7 | const selectGlobal = (state) => state.get('global');
8 |
9 | const makeSelectCurrentUser = () => createSelector(
10 | selectGlobal,
11 | (globalState) => globalState.get('currentUser')
12 | );
13 |
14 | const makeSelectLoading = () => createSelector(
15 | selectGlobal,
16 | (globalState) => globalState.get('loading')
17 | );
18 |
19 | const makeSelectError = () => createSelector(
20 | selectGlobal,
21 | (globalState) => globalState.get('error')
22 | );
23 |
24 | const makeSelectRepos = () => createSelector(
25 | selectGlobal,
26 | (globalState) => globalState.getIn(['userData', 'repositories'])
27 | );
28 |
29 | const makeSelectLocationState = () => {
30 | let prevRoutingState;
31 | let prevRoutingStateJS;
32 |
33 | return (state) => {
34 | const routingState = state.get('route'); // or state.route
35 |
36 | if (!routingState.equals(prevRoutingState)) {
37 | prevRoutingState = routingState;
38 | prevRoutingStateJS = routingState.toJS();
39 | }
40 |
41 | return prevRoutingStateJS;
42 | };
43 | };
44 |
45 | export {
46 | selectGlobal,
47 | makeSelectCurrentUser,
48 | makeSelectLoading,
49 | makeSelectError,
50 | makeSelectRepos,
51 | makeSelectLocationState,
52 | };
53 |
--------------------------------------------------------------------------------
/internals/templates/containers/LanguageProvider/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider
4 | *
5 | * this component connects the redux state language locale to the
6 | * IntlProvider component and i18n messages (loaded from `app/translations`)
7 | */
8 |
9 | import React from 'react';
10 | import { connect } from 'react-redux';
11 | import { createSelector } from 'reselect';
12 | import { IntlProvider } from 'react-intl';
13 |
14 | import { makeSelectLocale } from './selectors';
15 |
16 | export class LanguageProvider extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
17 | render() {
18 | return (
19 |
20 | {React.Children.only(this.props.children)}
21 |
22 | );
23 | }
24 | }
25 |
26 | LanguageProvider.propTypes = {
27 | locale: React.PropTypes.string,
28 | messages: React.PropTypes.object,
29 | children: React.PropTypes.element.isRequired,
30 | };
31 |
32 |
33 | const mapStateToProps = createSelector(
34 | makeSelectLocale(),
35 | (locale) => ({ locale })
36 | );
37 |
38 | function mapDispatchToProps(dispatch) {
39 | return {
40 | dispatch,
41 | };
42 | }
43 |
44 | export default connect(mapStateToProps, mapDispatchToProps)(LanguageProvider);
45 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageToggle
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { connect } from 'react-redux';
9 | import { createSelector } from 'reselect';
10 |
11 | import Toggle from 'components/Toggle';
12 | import Wrapper from './Wrapper';
13 | import messages from './messages';
14 | import { appLocales } from '../../i18n';
15 | import { changeLocale } from '../LanguageProvider/actions';
16 | import { makeSelectLocale } from '../LanguageProvider/selectors';
17 |
18 | export class LocaleToggle extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
19 | render() {
20 | return (
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | LocaleToggle.propTypes = {
29 | onLocaleToggle: React.PropTypes.func,
30 | locale: React.PropTypes.string,
31 | };
32 |
33 | const mapStateToProps = createSelector(
34 | makeSelectLocale(),
35 | (locale) => ({ locale })
36 | );
37 |
38 | export function mapDispatchToProps(dispatch) {
39 | return {
40 | onLocaleToggle: (evt) => dispatch(changeLocale(evt.target.value)),
41 | dispatch,
42 | };
43 | }
44 |
45 | export default connect(mapStateToProps, mapDispatchToProps)(LocaleToggle);
46 |
--------------------------------------------------------------------------------
/app/i18n.js:
--------------------------------------------------------------------------------
1 | /**
2 | * i18n.js
3 | *
4 | * This will setup the i18n language files and locale data for your app.
5 | *
6 | */
7 | import { addLocaleData } from 'react-intl';
8 | import enLocaleData from 'react-intl/locale-data/en';
9 | import deLocaleData from 'react-intl/locale-data/de';
10 |
11 | import { DEFAULT_LOCALE } from '../app/containers/App/constants';
12 |
13 | import enTranslationMessages from './translations/en.json';
14 | import deTranslationMessages from './translations/de.json';
15 |
16 | addLocaleData(enLocaleData);
17 | addLocaleData(deLocaleData);
18 |
19 | export const appLocales = [
20 | 'en',
21 | 'de',
22 | ];
23 |
24 | export const formatTranslationMessages = (locale, messages) => {
25 | const defaultFormattedMessages = locale !== DEFAULT_LOCALE
26 | ? formatTranslationMessages(DEFAULT_LOCALE, enTranslationMessages)
27 | : {};
28 | return Object.keys(messages).reduce((formattedMessages, key) => {
29 | const formattedMessage = !messages[key] && locale !== DEFAULT_LOCALE
30 | ? defaultFormattedMessages[key]
31 | : messages[key];
32 | return Object.assign(formattedMessages, { [key]: formattedMessage });
33 | }, {});
34 | };
35 |
36 | export const translationMessages = {
37 | en: formatTranslationMessages('en', enTranslationMessages),
38 | de: formatTranslationMessages('de', deTranslationMessages),
39 | };
40 |
--------------------------------------------------------------------------------
/app/containers/App/tests/actions.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | LOAD_REPOS,
3 | LOAD_REPOS_SUCCESS,
4 | LOAD_REPOS_ERROR,
5 | } from '../constants';
6 |
7 | import {
8 | loadRepos,
9 | reposLoaded,
10 | repoLoadingError,
11 | } from '../actions';
12 |
13 | describe('App Actions', () => {
14 | describe('loadRepos', () => {
15 | it('should return the correct type', () => {
16 | const expectedResult = {
17 | type: LOAD_REPOS,
18 | };
19 |
20 | expect(loadRepos()).toEqual(expectedResult);
21 | });
22 | });
23 |
24 | describe('reposLoaded', () => {
25 | it('should return the correct type and the passed repos', () => {
26 | const fixture = ['Test'];
27 | const username = 'test';
28 | const expectedResult = {
29 | type: LOAD_REPOS_SUCCESS,
30 | repos: fixture,
31 | username,
32 | };
33 |
34 | expect(reposLoaded(fixture, username)).toEqual(expectedResult);
35 | });
36 | });
37 |
38 | describe('repoLoadingError', () => {
39 | it('should return the correct type and the error', () => {
40 | const fixture = {
41 | msg: 'Something went wrong!',
42 | };
43 | const expectedResult = {
44 | type: LOAD_REPOS_ERROR,
45 | error: fixture,
46 | };
47 |
48 | expect(repoLoadingError(fixture)).toEqual(expectedResult);
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/app/containers/App/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * AppReducer
3 | *
4 | * The reducer takes care of our data. Using actions, we can change our
5 | * application state.
6 | * To add a new action, add it to the switch statement in the reducer function
7 | *
8 | * Example:
9 | * case YOUR_ACTION_CONSTANT:
10 | * return state.set('yourStateVariable', true);
11 | */
12 |
13 | import { fromJS } from 'immutable';
14 |
15 | import {
16 | LOAD_REPOS_SUCCESS,
17 | LOAD_REPOS,
18 | LOAD_REPOS_ERROR,
19 | } from './constants';
20 |
21 | // The initial state of the App
22 | const initialState = fromJS({
23 | loading: false,
24 | error: false,
25 | currentUser: false,
26 | userData: {
27 | repositories: false,
28 | },
29 | });
30 |
31 | function appReducer(state = initialState, action) {
32 | switch (action.type) {
33 | case LOAD_REPOS:
34 | return state
35 | .set('loading', true)
36 | .set('error', false)
37 | .setIn(['userData', 'repositories'], false);
38 | case LOAD_REPOS_SUCCESS:
39 | return state
40 | .setIn(['userData', 'repositories'], action.repos)
41 | .set('loading', false)
42 | .set('currentUser', action.username);
43 | case LOAD_REPOS_ERROR:
44 | return state
45 | .set('error', action.error)
46 | .set('loading', false);
47 | default:
48 | return state;
49 | }
50 | }
51 |
52 | export default appReducer;
53 |
--------------------------------------------------------------------------------
/docs/js/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript
2 |
3 | ## State management
4 |
5 | This boilerplate manages application state using [Redux](redux.md), makes it
6 | immutable with [`ImmutableJS`](immutablejs.md) and keeps access performant
7 | via [`reselect`](reselect.md).
8 |
9 | For managing asynchronous flows (e.g. logging in) we use [`redux-saga`](redux-saga.md).
10 |
11 | For routing, we use [`react-router` in combination with `react-router-redux`](routing.md).
12 |
13 | We include a generator for components, containers, sagas, routes and selectors.
14 | Run `npm run generate` to choose from the available generators, and automatically
15 | add new parts of your application!
16 |
17 | > Note: If you want to skip the generator selection process,
18 | `npm run generate ` also works. (e.g. `npm run generate route`)
19 |
20 | ### Learn more
21 |
22 | - [Redux](redux.md)
23 | - [ImmutableJS](immutablejs.md)
24 | - [reselect](reselect.md)
25 | - [redux-saga](redux-saga.md)
26 | - [react-intl](i18n.md)
27 | - [routing](routing.md)
28 |
29 | ## Architecture: `components` and `containers`
30 |
31 | We adopted a split between stateless, reusable components called (wait for it...)
32 | `components` and stateful parent components called `containers`.
33 |
34 | ### Learn more
35 |
36 | See [this article](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
37 | by Dan Abramov for a great introduction to this approach.
38 |
--------------------------------------------------------------------------------
/internals/templates/reducers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Combine all reducers in this file and export the combined reducers.
3 | * If we were to do this in store.js, reducers wouldn't be hot reloadable.
4 | */
5 |
6 | import { combineReducers } from 'redux-immutable';
7 | import { fromJS } from 'immutable';
8 | import { LOCATION_CHANGE } from 'react-router-redux';
9 |
10 | import languageProviderReducer from 'containers/LanguageProvider/reducer';
11 |
12 | /*
13 | * routeReducer
14 | *
15 | * The reducer merges route location changes into our immutable state.
16 | * The change is necessitated by moving to react-router-redux@4
17 | *
18 | */
19 |
20 | // Initial routing state
21 | const routeInitialState = fromJS({
22 | locationBeforeTransitions: null,
23 | });
24 |
25 | /**
26 | * Merge route into the global application state
27 | */
28 | function routeReducer(state = routeInitialState, action) {
29 | switch (action.type) {
30 | /* istanbul ignore next */
31 | case LOCATION_CHANGE:
32 | return state.merge({
33 | locationBeforeTransitions: action.payload,
34 | });
35 | default:
36 | return state;
37 | }
38 | }
39 |
40 | /**
41 | * Creates the main reducer with the asynchronously loaded ones
42 | */
43 | export default function createReducer(asyncReducers) {
44 | return combineReducers({
45 | route: routeReducer,
46 | language: languageProviderReducer,
47 | ...asyncReducers,
48 | });
49 | }
50 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # http://www.appveyor.com/docs/appveyor-yml
2 |
3 | # Set build version format here instead of in the admin panel
4 | version: "{build}"
5 |
6 | # Do not build on gh tags
7 | skip_tags: true
8 |
9 | # Test against these versions of Node.js
10 | environment:
11 |
12 | matrix:
13 | # Node versions to run
14 | - nodejs_version: 7
15 | - nodejs_version: 6
16 | - nodejs_version: 5
17 |
18 | # Fix line endings in Windows. (runs before repo cloning)
19 | init:
20 | - git config --global core.autocrlf input
21 |
22 | # Install scripts--runs after repo cloning
23 | install:
24 | # Install chrome
25 | - choco install -y googlechrome
26 | # Install the latest stable version of Node
27 | - ps: Install-Product node $env:nodejs_version
28 | - set PATH=%APPDATA%\yarn;%PATH%
29 | - yarn
30 |
31 | # Disable automatic builds
32 | build: off
33 |
34 | # Post-install test scripts
35 | test_script:
36 | # Output debugging info
37 | - node --version
38 | - node ./internals/scripts/generate-templates-for-linting
39 | # run tests and run build
40 | - yarn run test
41 | - yarn run build
42 |
43 | # Cache node_modules for faster builds
44 | cache:
45 | - "%LOCALAPPDATA%\\Yarn"
46 | - node_modules -> package.json
47 |
48 | # remove, as appveyor doesn't support secure variables on pr builds
49 | # so `COVERALLS_REPO_TOKEN` cannot be set, without hard-coding in this file
50 | #on_success:
51 | #- yarn run coveralls
52 |
--------------------------------------------------------------------------------
/docs/css/styled-components.md:
--------------------------------------------------------------------------------
1 | # `styled-components`
2 |
3 | `styled-components` allow you to write actual CSS code in your JavaScript to style your components,
4 | removing the mapping between components and styles.
5 |
6 | See the
7 | [official documentation](https://github.com/styled-components/styled-components)
8 | for more information!
9 |
10 | ## Usage
11 |
12 | This creates two react components, `` and ``:
13 |
14 | ```JSX
15 | import React from 'react';
16 |
17 | import styled from 'styled-components';
18 |
19 | // Create a react component that renders an