Skip to content

Commit

Permalink
Redux with stub data (fixes #8)
Browse files Browse the repository at this point in the history
  • Loading branch information
mstriemer committed Mar 6, 2016
1 parent f4e0948 commit 280a75f
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 29 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
"express": "4.13.4",
"helmet": "1.3.0",
"react": "0.14.7",
"react-router": "2.0.0"
"react-redux": "4.4.0",
"react-router": "2.0.0",
"redux": "3.3.1"
},
"devDependencies": {
"babel-core": "6.6.4",
Expand Down Expand Up @@ -68,6 +70,7 @@
"react-dom": "0.14.7",
"react-hot-loader": "1.3.0",
"react-transform-hmr": "1.0.2",
"redux-devtools": "3.1.1",
"semver": "5.1.0",
"shelljs": "0.6.0",
"sinon": "1.17.3",
Expand Down
15 changes: 15 additions & 0 deletions src/core/reducers/addons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const initialState = {
wat: {slug: 'wat', title: 'Wat is this?'},
foo: {slug: 'foo', title: 'The foo add-on'},
food: {slug: 'food', title: 'Find food'},
bar: {slug: 'bar', title: 'The bar add-on'},
};

export default function addon(state = initialState, action) {
switch (action.type) {
case 'ADDON_FETCHED':
return Object.assign({}, state, {[action.addon.slug]: action.addon});
default:
return state;
}
}
6 changes: 6 additions & 0 deletions src/search/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function setQuery(query) {
return {
type: 'SET_QUERY',
query,
};
}
1 change: 1 addition & 0 deletions src/search/client.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'babel-core/polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';
Expand Down
42 changes: 14 additions & 28 deletions src/search/components/App.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
import React from 'react';

import SearchForm from './SearchForm';
import SearchResults from './SearchResults';
import { gettext as _ } from 'core/utils';

export default class App extends React.Component {

state = {
query: null,
results: [],
}

handleSearch = (query) => {
const results = [{title: 'Foo'}, {title: 'Bar'}, {title: 'Baz'}];
this.setState({ query, results });
}

render() {
const { query, results } = this.state;
return (
<div className="search-app">
<h1>{_('Add-on Search')}</h1>
<SearchForm onSearch={this.handleSearch} />
<SearchResults results={results} query={query} />
</div>
);
}
}
import { Provider } from 'react-redux';
import { createStore, combineReducers } from 'redux';
import CurrentSearchPage from '../containers/CurrentSearchPage';
import search from '../reducers/search';
import addons from 'core/reducers/addons';
const store = createStore(combineReducers({addons, search}));

const App = () => (
<Provider store={store}>
<CurrentSearchPage />
</Provider>
);

export default App;
24 changes: 24 additions & 0 deletions src/search/components/SearchPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { PropTypes } from 'react';

import SearchForm from './SearchForm';
import SearchResults from './SearchResults';
import { gettext as _ } from 'core/utils';

const SearchPage = ({ handleSearch, results, query }) => (
<div className="search-page">
<h1>{_('Add-on Search')}</h1>
<SearchForm onSearch={handleSearch} />
<SearchResults results={results} query={query} />
</div>
);

SearchPage.propTypes = {
handleSearch: PropTypes.func.isRequired,
results: PropTypes.arrayOf(PropTypes.shape({
slug: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
})),
query: PropTypes.string,
};

export default SearchPage;
35 changes: 35 additions & 0 deletions src/search/containers/CurrentSearchPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { connect } from 'react-redux';
import SearchPage from '../components/SearchPage';
import { setQuery } from '../actions';

function getVisibleAddons(addons, query) {
const matches = [];
for (const slug in addons) {
if (addons[slug].title.indexOf(query) >= 0) {
matches.push(addons[slug]);
}
}
return matches;
}

function mapStateToProps(state) {
return {
results: getVisibleAddons(state.addons, state.search.query),
query: state.search.query,
};
}

function mapDispatchToProps(dispatch) {
return {
handleSearch: (query) => {
dispatch(setQuery(query));
},
};
}

const CurrentSearchPage = connect(
mapStateToProps,
mapDispatchToProps,
)(SearchPage);

export default CurrentSearchPage;
12 changes: 12 additions & 0 deletions src/search/reducers/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const initialState = {
query: null,
};

export default function search(state = initialState, action) {
switch (action.type) {
case 'SET_QUERY':
return Object.assign({}, state, {query: action.query});
default:
return state;
}
}
4 changes: 4 additions & 0 deletions test-runner.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
require('babel-core/polyfill');

const testsContext = require.context('./tests', true, /\.js$/);
const componentsContext = require.context('./src/', true, /components\/.*\.js$/);
const reducersContext = require.context('./src/', true, /reducers\/.*\.js$/);

testsContext.keys().forEach(testsContext);
componentsContext.keys().forEach(componentsContext);
reducersContext.keys().forEach(reducersContext);
17 changes: 17 additions & 0 deletions tests/core/reducers/test_addons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import addons from 'core/reducers/addons';

describe('addon reducer', () => {
it('returns the old state', () => {
const originalState = {foo: {slug: 'foo'}, bar: {slug: 'bar'}};
assert.strictEqual(originalState, addons(originalState, {type: 'BLAH'}));
});

it('adds an addon', () => {
const originalState = {foo: {slug: 'foo'}};
const baz = {slug: 'baz'};
const expectedState = {foo: {slug: 'foo'}, baz};
const newState = addons(originalState, {type: 'ADDON_FETCHED', addon: baz});
assert.notStrictEqual(originalState, newState);
assert.deepEqual(expectedState, newState);
});
});
8 changes: 8 additions & 0 deletions tests/search/reducers/test_search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import search from 'search/reducers/search';

describe('search reducer', () => {
it('has a null query', () => {
const { query } = search(undefined, {type: '@@INIT'});
assert.strictEqual(query, null);
});
});

0 comments on commit 280a75f

Please sign in to comment.