Skip to content
This repository has been archived by the owner on Jan 21, 2024. It is now read-only.

Implement redux-connect to allow server-side rendering #7

Merged
merged 1 commit into from
Jun 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"dependencies": {
"autoprefixer": "^6.3.6",
"config": "^1.21.0",
"isomorphic-fetch": "^2.2.1",
"normalizr": "^2.1.0",
"postcss-import": "^8.1.2",
"postcss-nested": "^1.0.0",
Expand All @@ -42,6 +43,7 @@
"react-router": "^2.4.1",
"redux": "^3.5.2",
"redux-actions": "^0.10.0",
"redux-connect": "^2.4.0",
"redux-promise": "^0.5.3",
"redux-thunk": "^2.1.0",
"reselect": "^2.5.1",
Expand All @@ -62,7 +64,9 @@
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.5.0",
"chai": "^3.5.0",
"chai-as-promised": "^5.3.0",
"chai-enzyme": "^0.5.0",
"chai-things": "^0.2.0",
"cheerio": "^0.20.0",
"coveralls": "^2.11.9",
"css-loader": "^0.23.1",
Expand Down Expand Up @@ -91,6 +95,8 @@
"react-transform-catch-errors": "^1.0.2",
"react-transform-hmr": "^1.0.4",
"redbox-react": "^1.2.5",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0",
"style-loader": "^0.13.1",
"webpack-dev-middleware": "^1.6.1",
"webpack-dev-server": "^1.14.1",
Expand Down
3 changes: 2 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'babel-polyfill';
import React from 'react';
import { Router, Route, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { ReduxAsyncConnect } from 'redux-connect';
import { Home } from 'containers';
import { configureStore } from './store';

Expand All @@ -13,7 +14,7 @@ const App = ({
const store = configureStore({ initialState, enhancers });
return (
<Provider store={store} key="provider">
<Router history={browserHistory}>
<Router history={browserHistory} render={(props) => <ReduxAsyncConnect {...props} />}>
<Route path="/" component={Home} />
</Router>
</Provider>
Expand Down
42 changes: 16 additions & 26 deletions src/components/Home/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,27 @@ const Home = ({
isSelected,
matchingProducts,
match,
fetchFeatures,
fetchProducts,
selectFeature,
ignoreFeature
}) => {
if (!allFeatures) {
// Initialize features and products
fetchFeatures();
fetchProducts();
}

return (
<div className={styles.home}>
<div className={styles.banner}>
<div className={styles.logo}></div>
<div className={styles.info}>
<h1 className={styles.title}>Mule match</h1>
<p className={styles.description}>Find the right app for you with this simple swiping app!</p>
</div>
}) => (
<div className={styles.home}>
<div className={styles.banner}>
<div className={styles.logo}></div>
<div className={styles.info}>
<h1 className={styles.title}>Mule match</h1>
<p className={styles.description}>Find the right app for you with this simple swiping app!</p>
</div>
<div className={styles.body}>
<div className={styles.features} display-if={allFeatures}>
<FeatureList allFeatures={allFeatures} isSelected={isSelected} />
<ControlPanel currentFeature={currentFeature} selectFeature={selectFeature} ignoreFeature={ignoreFeature} />
</div>
<Loading display-if={!allFeatures} />
</div>
<div className={styles.body}>
<div className={styles.features} display-if={allFeatures}>
<FeatureList allFeatures={allFeatures} isSelected={isSelected} />
<ControlPanel currentFeature={currentFeature} selectFeature={selectFeature} ignoreFeature={ignoreFeature} />
</div>
<Match display-if={match || (!currentFeature && matchingProducts)} match={match} potentialMatches={matchingProducts} />
<Loading display-if={!allFeatures} />
</div>
);
};
<Match display-if={match || (!currentFeature && matchingProducts)} match={match} potentialMatches={matchingProducts} />
</div>
);

Home.propTypes = propTypes;
export default Home;
13 changes: 10 additions & 3 deletions src/containers/Home/Home.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import { Home } from 'components';
import async from 'containers/async';
import {
actions,
selectors
Expand All @@ -14,10 +15,16 @@ const homeState = (state) => ({
});

const homeActions = (dispatch) => ({
fetchFeatures: (payload) => dispatch(actions.fetchFeatures(payload)),
fetchProducts: (payload) => dispatch(actions.fetchProducts(payload)),
selectFeature: (payload) => dispatch(actions.selectFeature(payload)),
ignoreFeature: (payload) => dispatch(actions.ignoreFeature(payload))
});

export default connect(homeState, homeActions)(Home);
const resolve = ({ dispatch }) => {
// Initialize features and products
const featuresPromise = dispatch(actions.fetchFeatures());
const productsPromise = dispatch(actions.fetchProducts());

return Promise.all([featuresPromise, productsPromise]);
};

export default async(resolve)(connect(homeState, homeActions)(Home));
21 changes: 21 additions & 0 deletions src/containers/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { asyncConnect } from 'redux-connect';

const defaultPromise = Promise.resolve();

const async = (
resolve = defaultPromise
) => (
asyncConnect([{
promise: (options) => {
const payload = {
...options,
getState: options.store.getState,
dispatch: options.store.dispatch
};

return resolve(payload);
}
}])
);

export default async;
5 changes: 2 additions & 3 deletions src/domains/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { combineReducers } from 'redux';
import {
actions as featuresActions,
actionTypes as featuresActionTypes,
Expand Down Expand Up @@ -32,11 +31,11 @@ const selectors = {
...uiSelectors
};

const reducers = combineReducers({
const reducers = {
features: featuresReducers,
products: productsReducers,
ui: uiReducers
});
};

export {
actionTypes,
Expand Down
2 changes: 2 additions & 0 deletions src/services/featureService.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'isomorphic-fetch';

const baseUri = CONFIG.BACKEND.URI;

const featureService = {
Expand Down
2 changes: 2 additions & 0 deletions src/services/productService.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'isomorphic-fetch';

const baseUri = CONFIG.BACKEND.URI;

const productService = {
Expand Down
9 changes: 5 additions & 4 deletions src/store/configureStore.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { applyMiddleware, createStore, compose } from 'redux';
import promiseMiddleware from 'redux-promise';
import { reducers } from 'domains';
import { applyMiddleware, createStore, compose, combineReducers } from 'redux';
import promiseMiddleware from 'redux-promise';
import { reducer as reduxAsyncReducer } from 'redux-connect';
import { reducers } from 'domains';

const defaultState = {};
const defaultEnhancers = applyMiddleware(promiseMiddleware);
Expand All @@ -10,7 +11,7 @@ const configureStore = ({
enhancers = []
}) => (
createStore(
reducers,
combineReducers({ ...reducers, reduxAsyncConnect: reduxAsyncReducer }),
initialState,
compose(defaultEnhancers, enhancers)
)
Expand Down
13 changes: 10 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'babel-polyfill';
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import sinonChai from 'sinon-chai';
import chaiThings from 'chai-things';
import chaiEnzyme from 'chai-enzyme';

// To create a code coverage report for all components you have to require all the sources and test
// See https://github.com/deepsweet/isparta-loader
Expand All @@ -26,5 +29,9 @@ const servicesContext = require.context('../src/services/', false, /index\.js$/)
servicesContext.keys().forEach(servicesContext);

// Setup libraries used for testing
global.should = chai.should();
chai.use(chaiAsPromised);
chai.use(sinonChai);
chai.use(chaiThings);
chai.use(chaiEnzyme());

global.should = chai.should();
2 changes: 0 additions & 2 deletions test/src/components/Home.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ describe('Home', () => {
isSelected = true;
matchingProducts = [];
match = {};
fetchFeatures = () => {};
fetchProducts = () => {};
selectFeature = () => {};
ignoreFeature = () => {};
component = shallow(
Expand Down
32 changes: 32 additions & 0 deletions test/src/services/featureService.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import sinon from 'sinon';
import { featureService } from 'services';

describe('featureService', () => {
let response;
let expectedResult;
let mockResponseJson;
let mockFetch;
let result;

beforeEach(() => {
response = { json: () => {} };
expectedResult = { status: 200, data: 'result' };

mockResponseJson = sinon.stub(response, 'json').returns(Promise.resolve(expectedResult));
mockFetch = sinon.stub(window, 'fetch').returns(Promise.resolve(response));
});

afterEach(() => {
window.fetch.restore();
});

describe('getFeatures', () => {
beforeEach(async () => {
result = await featureService.getFeatures();
});

it('should call fetch to retrieve the features', () => mockFetch.should.have.been.called);
it('should convert the result to JSON', () => mockResponseJson.should.have.been.called);
it('should retrieve the result', () => result.should.be.equal(expectedResult));
});
});
32 changes: 32 additions & 0 deletions test/src/services/productService.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import sinon from 'sinon';
import { productService } from 'services';

describe('productService', () => {
let response;
let expectedResult;
let mockResponseJson;
let mockFetch;
let result;

beforeEach(() => {
response = { json: () => {} };
expectedResult = { status: 200, data: 'result' };

mockResponseJson = sinon.stub(response, 'json').returns(Promise.resolve(expectedResult));
mockFetch = sinon.stub(window, 'fetch').returns(Promise.resolve(response));
});

afterEach(() => {
window.fetch.restore();
});

describe('getProducts', () => {
beforeEach(async () => {
result = await productService.getProducts();
});

it('should call fetch to retrieve the items', () => mockFetch.should.have.been.called);
it('should convert the result to JSON', () => mockResponseJson.should.have.been.called);
it('should retrieve the result', () => result.should.be.equal(expectedResult));
});
});
4 changes: 3 additions & 1 deletion webpack/configs/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ var webpack = require('webpack');
var aliases = require('../aliases');
var rootPath = path.resolve(__dirname, '../../');

aliases['test'] = 'src/test/src';
// Add new keys for tests
aliases['test'] = 'src/test/src';
aliases['sinon'] = 'sinon/pkg/sinon',

module.exports = {
devtool: 'inline-source-map',
Expand Down