Remove examples feature

Change-Id: Id347a0a8b62c3f9df7e5b2e06a0daf4e989626dd
This commit is contained in:
Manuel Stahl 2019-02-07 12:51:30 +01:00
parent deae3535bc
commit 3749005995
34 changed files with 0 additions and 949 deletions

View File

@ -2,7 +2,6 @@ import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux'; import { routerReducer } from 'react-router-redux';
import homeReducer from '../features/home/redux/reducer'; import homeReducer from '../features/home/redux/reducer';
import commonReducer from '../features/common/redux/reducer'; import commonReducer from '../features/common/redux/reducer';
import examplesReducer from '../features/examples/redux/reducer';
// NOTE 1: DO NOT CHANGE the 'reducerMap' name and the declaration pattern. // NOTE 1: DO NOT CHANGE the 'reducerMap' name and the declaration pattern.
// This is used for Rekit cmds to register new features, remove features, etc. // This is used for Rekit cmds to register new features, remove features, etc.
@ -13,7 +12,6 @@ const reducerMap = {
router: routerReducer, router: routerReducer,
home: homeReducer, home: homeReducer,
common: commonReducer, common: commonReducer,
examples: examplesReducer,
}; };
export default combineReducers(reducerMap); export default combineReducers(reducerMap);

View File

@ -2,7 +2,6 @@ import { App } from '../features/home';
import { PageNotFound } from '../features/common'; import { PageNotFound } from '../features/common';
import homeRoute from '../features/home/route'; import homeRoute from '../features/home/route';
import commonRoute from '../features/common/route'; import commonRoute from '../features/common/route';
import examplesRoute from '../features/examples/route';
import _ from 'lodash'; import _ from 'lodash';
// NOTE: DO NOT CHANGE the 'childRoutes' name and the declaration pattern. // NOTE: DO NOT CHANGE the 'childRoutes' name and the declaration pattern.
@ -10,7 +9,6 @@ import _ from 'lodash';
const childRoutes = [ const childRoutes = [
homeRoute, homeRoute,
commonRoute, commonRoute,
examplesRoute,
]; ];
const routes = [{ const routes = [{

View File

@ -1,45 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from './redux/actions';
export class CounterPage extends Component {
static propTypes = {
examples: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
};
render() {
return (
<div className="examples-counter-page">
<h1>Counter</h1>
<p>This is simple counter demo to show how Redux sync actions work.</p>
<button className="btn-minus-one" onClick={this.props.actions.counterMinusOne} disabled={this.props.examples.count === 0}>
-
</button>
<span>{this.props.examples.count}</span>
<button className="btn-plus-one" onClick={this.props.actions.counterPlusOne}>+</button>
<button className="btn-reset" onClick={this.props.actions.counterReset}>
Reset
</button>
</div>
);
}
}
/* istanbul ignore next */
function mapStateToProps(state) {
return {
examples: state.examples,
};
}
/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ ...actions }, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(CounterPage);

View File

@ -1,18 +0,0 @@
@import '../../styles/mixins';
.examples-counter-page {
color: #555;
h1 {
margin-top: 0px;
font-size: 28px;
}
span {
padding: 0 10px;
display: inline-block;
min-width: 30px;
text-align: center;
}
button.btn-reset {
margin-left: 15px;
}
}

View File

@ -1,20 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { SidePanel } from './';
export default class Layout extends Component {
static propTypes = {
children: PropTypes.node,
};
render() {
return (
<div className="examples-layout">
<SidePanel />
<div className="examples-page-container">
{this.props.children}
</div>
</div>
);
}
}

View File

@ -1,9 +0,0 @@
@import '../../styles/mixins';
.examples-layout {
.examples-page-container {
padding: 40px 30px 0 300px;
min-width: 400px;
max-width: 800px;
}
}

View File

@ -1,57 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { fetchRedditList } from './redux/actions';
export class RedditListPage extends Component {
static propTypes = {
examples: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
};
render() {
const { fetchRedditListPending, redditList, fetchRedditListError } = this.props.examples;
const { fetchRedditList } = this.props.actions;
return (
<div className="examples-reddit-list-page">
<h1>Reddit API Usage</h1>
<p>This demo shows how to use Redux async actions to fetch data from Reddit's REST API.</p>
<button className="btn-fetch-reddit" disabled={fetchRedditListPending} onClick={fetchRedditList}>
{fetchRedditListPending ? 'Fetching...' : 'Fetch reactjs topics'}
</button>
{fetchRedditListError && (
<div className="fetch-list-error">Failed to load: {fetchRedditListError.toString()}</div>
)}
{redditList.length > 0 ? (
<ul className="examples-reddit-list">
{redditList.map(item => (
<li key={item.data.id}>
<a href={item.data.url}>{item.data.title}</a>
</li>
))}
</ul>
) : (
<div className="no-items-tip">No items yet.</div>
)}
</div>
);
}
}
/* istanbul ignore next */
function mapStateToProps(state) {
return {
examples: state.examples,
};
}
/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ fetchRedditList }, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(RedditListPage);

View File

@ -1,24 +0,0 @@
@import '../../styles/mixins';
.examples-reddit-list-page {
color: #555;
h1 {
margin-top: 0px;
font-size: 28px;
}
a {
color: #2db7f5;
text-decoration: none;
&:hover {
color: #f90;
}
}
.fetch-list-error {
display: block;
margin-top: 20px;
color: red;
}
.no-items-tip {
margin-top: 15px;
}
}

View File

@ -1,54 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from './redux/actions';
export class SidePanel extends Component {
static propTypes = {
examples: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
};
render() {
return (
<div className="examples-side-panel">
<ul>
<li>
<Link to="/examples">Welcome</Link>
</li>
<li>
<Link to="/examples/counter">Counter Demo</Link>
</li>
<li>
<Link to="/examples/reddit">Reddit API Demo</Link>
</li>
<li>
<Link to="/">Back to start page</Link>
</li>
</ul>
<div className="memo">
This is a Rekit feature that contains some examples for you to quick learn how Rekit works. To remove it just
delete the feature.
</div>
</div>
);
}
}
/* istanbul ignore next */
function mapStateToProps(state) {
return {
examples: state.examples,
};
}
/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ ...actions }, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SidePanel);

View File

@ -1,46 +0,0 @@
@import '../../styles/mixins';
.examples-side-panel {
position: fixed;
box-sizing: border-box;
overflow: auto;
top: 0;
left: 0;
margin: 0;
padding: 40px;
width: 260px;
height: 100%;
background-color: #f7f7f7;
ul,
li {
padding: 0;
margin: 0;
list-style: none;
}
li {
padding: 8px;
}
a {
color: #2db7f5;
text-decoration: none;
&:hover {
color: #f90;
}
}
.memo {
&:before {
content: ' ';
display: block;
height: 2px;
width: 60px;
margin-bottom: 10px;
background-color: #ddd;
}
font-size: 13px;
margin-top: 40px;
line-height: 150%;
color: #aaa;
}
}

View File

@ -1,57 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import rekitLogo from '../../images/rekit-logo.svg';
import * as actions from './redux/actions';
export class WelcomePage extends Component {
static propTypes = {
examples: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
};
render() {
return (
<div className="examples-welcome-page">
<a href="http://github.com/supnate/rekit">
<img src={rekitLogo} className="app-logo" alt="logo" />
</a>
<h1>Welcome to Rekit!</h1>
<p>
Contratulations! You have created your Rekit app successfully! Seeing this page means everything works well
now.
</p>
<p>
By default <a href="https://github.com/supnate/rekit">Rekit Studio</a> is also started at{' '}
<a href="http://localhost:6076">http://localhost:6076</a> to manage the project.
</p>
<p>
This is an example feature showing about how to layout the container, how to use Redux and React Router. If
you want to remove all sample code, just delete the feature from Rekit Studio. Alternatively you can run&nbsp;
<code>rekit remove feature examples</code> by command line under the project folder.
</p>
<p>
To learn more about how to get started, you can visit:{' '}
<a href="http://rekit.js.org/docs/get-started.html">Get started</a>
</p>
</div>
);
}
}
/* istanbul ignore next */
function mapStateToProps(state) {
return {
examples: state.examples,
};
}
/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ ...actions }, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(WelcomePage);

View File

@ -1,36 +0,0 @@
@import '../../styles/mixins';
.examples-welcome-page {
line-height: 160%;
position: relative;
color: #555;
a {
color: #2db7f5;
text-decoration: none;
&:hover {
color: #f90;
}
}
.app-logo {
position: absolute;
top: -14px;
left: 0px;
width: 50px;
}
h1 {
padding-left: 60px;
margin-bottom: 40px;
font-size: 28px;
}
h3 {
margin-top: 20px;
}
code {
font-size: 14px;
}
}

View File

@ -1,5 +0,0 @@
export { default as SidePanel } from './SidePanel';
export { default as WelcomePage } from './WelcomePage';
export { default as CounterPage } from './CounterPage';
export { default as RedditListPage } from './RedditListPage';
export { default as Layout } from './Layout';

View File

@ -1,4 +0,0 @@
export { counterPlusOne } from './counterPlusOne';
export { counterMinusOne } from './counterMinusOne';
export { counterReset } from './counterReset';
export { fetchRedditList, dismissFetchRedditListError } from './fetchRedditList';

View File

@ -1,7 +0,0 @@
export const EXAMPLES_COUNTER_PLUS_ONE = 'EXAMPLES_COUNTER_PLUS_ONE';
export const EXAMPLES_COUNTER_MINUS_ONE = 'EXAMPLES_COUNTER_MINUS_ONE';
export const EXAMPLES_COUNTER_RESET = 'EXAMPLES_COUNTER_RESET';
export const EXAMPLES_FETCH_REDDIT_LIST_BEGIN = 'EXAMPLES_FETCH_REDDIT_LIST_BEGIN';
export const EXAMPLES_FETCH_REDDIT_LIST_SUCCESS = 'EXAMPLES_FETCH_REDDIT_LIST_SUCCESS';
export const EXAMPLES_FETCH_REDDIT_LIST_FAILURE = 'EXAMPLES_FETCH_REDDIT_LIST_FAILURE';
export const EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR = 'EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR';

View File

@ -1,24 +0,0 @@
// Rekit uses a new approach to organizing actions and reducers. That is
// putting related actions and reducers in one file. See more at:
// https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da
import { EXAMPLES_COUNTER_MINUS_ONE } from './constants';
export function counterMinusOne() {
return {
type: EXAMPLES_COUNTER_MINUS_ONE,
};
}
export function reducer(state, action) {
switch (action.type) {
case EXAMPLES_COUNTER_MINUS_ONE:
return {
...state,
count: state.count - 1,
};
default:
return state;
}
}

View File

@ -1,24 +0,0 @@
// Rekit uses a new approach to organizing actions and reducers. That is
// putting related actions and reducers in one file. See more at:
// https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da
import { EXAMPLES_COUNTER_PLUS_ONE } from './constants';
export function counterPlusOne() {
return {
type: EXAMPLES_COUNTER_PLUS_ONE,
};
}
export function reducer(state, action) {
switch (action.type) {
case EXAMPLES_COUNTER_PLUS_ONE:
return {
...state,
count: state.count + 1,
};
default:
return state;
}
}

View File

@ -1,24 +0,0 @@
// Rekit uses a new approach to organizing actions and reducers. That is
// putting related actions and reducers in one file. See more at:
// https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da
import { EXAMPLES_COUNTER_RESET } from './constants';
export function counterReset() {
return {
type: EXAMPLES_COUNTER_RESET,
};
}
export function reducer(state, action) {
switch (action.type) {
case EXAMPLES_COUNTER_RESET:
return {
...state,
count: 0,
};
default:
return state;
}
}

View File

@ -1,97 +0,0 @@
import axios from 'axios';
import {
EXAMPLES_FETCH_REDDIT_LIST_BEGIN,
EXAMPLES_FETCH_REDDIT_LIST_SUCCESS,
EXAMPLES_FETCH_REDDIT_LIST_FAILURE,
EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR,
} from './constants';
// Rekit uses redux-thunk for async actions by default: https://github.com/gaearon/redux-thunk
// If you prefer redux-saga, you can use rekit-plugin-redux-saga: https://github.com/supnate/rekit-plugin-redux-saga
export function fetchRedditList(args = {}) {
return dispatch => {
// optionally you can have getState as the second argument
dispatch({
type: EXAMPLES_FETCH_REDDIT_LIST_BEGIN,
});
// Return a promise so that you could control UI flow without states in the store.
// For example: after submit a form, you need to redirect the page to another when succeeds or show some errors message if fails.
// It's hard to use state to manage it, but returning a promise allows you to easily achieve it.
// e.g.: handleSubmit() { this.props.actions.submitForm(data).then(()=> {}).catch(() => {}); }
const promise = new Promise((resolve, reject) => {
// doRequest is a placeholder Promise. You should replace it with your own logic.
// See the real-word example at: https://github.com/supnate/rekit/blob/master/src/features/home/redux/fetchRedditReactjsList.js
// args.error here is only for test coverage purpose.
const doRequest = axios.get('http://www.reddit.com/r/reactjs.json');
doRequest.then(
res => {
dispatch({
type: EXAMPLES_FETCH_REDDIT_LIST_SUCCESS,
data: res.data,
});
resolve(res);
},
// Use rejectHandler as the second argument so that render errors won't be caught.
err => {
dispatch({
type: EXAMPLES_FETCH_REDDIT_LIST_FAILURE,
data: { error: err },
});
reject(err);
}
);
});
return promise;
};
}
// Async action saves request error by default, this method is used to dismiss the error info.
// If you don't want errors to be saved in Redux store, just ignore this method.
export function dismissFetchRedditListError() {
return {
type: EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR,
};
}
export function reducer(state, action) {
switch (action.type) {
case EXAMPLES_FETCH_REDDIT_LIST_BEGIN:
// Just after a request is sent
return {
...state,
fetchRedditListPending: true,
fetchRedditListError: null,
};
case EXAMPLES_FETCH_REDDIT_LIST_SUCCESS:
// The request is success
return {
...state,
redditList: action.data.data.children,
fetchRedditListPending: false,
fetchRedditListError: null,
};
case EXAMPLES_FETCH_REDDIT_LIST_FAILURE:
// The request is failed
return {
...state,
fetchRedditListPending: false,
fetchRedditListError: action.data.error,
};
case EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR:
// Dismiss the request failure error
return {
...state,
fetchRedditListError: null,
};
default:
return state;
}
}

View File

@ -1,15 +0,0 @@
// Initial state is the place you define all initial values for the Redux store of the feature.
// In the 'standard' way, initialState is defined in reducers: http://redux.js.org/docs/basics/Reducers.html
// But when application grows, there will be multiple reducers files, it's not intuitive what data is managed by the whole store.
// So Rekit extracts the initial state definition into a separate module so that you can have
// a quick view about what data is used for the feature, at any time.
// NOTE: initialState constant is necessary so that Rekit could auto add initial state when creating async actions.
const initialState = {
count: 0,
redditList: [],
fetchRedditListPending: false,
fetchRedditListError: null,
};
export default initialState;

View File

@ -1,31 +0,0 @@
// This is the root reducer of the feature. It is used for:
// 1. Load reducers from each action in the feature and process them one by one.
// Note that this part of code is mainly maintained by Rekit, you usually don't need to edit them.
// 2. Write cross-topic reducers. If a reducer is not bound to some specific action.
// Then it could be written here.
// Learn more from the introduction of this approach:
// https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da.
import initialState from './initialState';
import { reducer as counterPlusOneReducer } from './counterPlusOne';
import { reducer as counterMinusOneReducer } from './counterMinusOne';
import { reducer as counterResetReducer } from './counterReset';
import { reducer as fetchRedditListReducer } from './fetchRedditList';
const reducers = [
counterPlusOneReducer,
counterMinusOneReducer,
counterResetReducer,
fetchRedditListReducer,
];
export default function reducer(state = initialState, action) {
let newState;
switch (action.type) {
// Handle cross-topic actions here
default:
newState = state;
break;
}
return reducers.reduce((s, r) => r(s, action), newState);
}

View File

@ -1,20 +0,0 @@
// This is the JSON way to define React Router rules in a Rekit app.
// Learn more from: http://rekit.js.org/docs/routing.html
import {
WelcomePage,
CounterPage,
RedditListPage,
Layout,
} from './';
export default {
path: 'examples',
name: 'Examples',
component: Layout,
childRoutes: [
{ path: '', name: 'Welcome page', component: WelcomePage },
{ path: 'counter', name: 'Counter page', component: CounterPage },
{ path: 'reddit', name: 'Reddit list page', component: RedditListPage },
],
};

View File

@ -1,6 +0,0 @@
@import '../../styles/mixins';
@import './SidePanel';
@import './WelcomePage';
@import './CounterPage';
@import './RedditListPage';
@import './Layout';

View File

@ -2,4 +2,3 @@
@import './global'; @import './global';
@import '../features/home/style'; @import '../features/home/style';
@import '../features/common/style'; @import '../features/common/style';
@import '../features/examples/style';

View File

@ -1,35 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { CounterPage } from '../../../src/features/examples/CounterPage';
describe('examples/CounterPage', () => {
it('renders node with correct class name', () => {
const props = {
examples: {},
actions: {},
};
const renderedComponent = shallow(<CounterPage {...props} />);
expect(renderedComponent.find('.examples-counter-page').length).toBe(1);
});
it('counter actions are called when buttons clicked', () => {
const pageProps = {
examples: {},
actions: {
counterPlusOne: jest.fn(),
counterMinusOne: jest.fn(),
counterReset: jest.fn(),
},
};
const renderedComponent = shallow(
<CounterPage {...pageProps} />
);
renderedComponent.find('.btn-plus-one').simulate('click');
renderedComponent.find('.btn-minus-one').simulate('click');
renderedComponent.find('.btn-reset').simulate('click');
expect(pageProps.actions.counterPlusOne.mock.calls.length).toBe(1);
expect(pageProps.actions.counterMinusOne.mock.calls.length).toBe(1);
expect(pageProps.actions.counterReset.mock.calls.length).toBe(1);
});
});

View File

@ -1,11 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Layout } from '../../../src/features/examples';
describe('examples/Layout', () => {
it('renders node with correct class name', () => {
const renderedComponent = shallow(<Layout />);
expect(renderedComponent.find('.examples-layout').length).toBe(1);
});
});

View File

@ -1,51 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { RedditListPage } from '../../../src/features/examples/RedditListPage';
describe('examples/RedditListPage', () => {
it('renders node with correct class name', () => {
const props = {
examples: { redditList: [] },
actions: {},
};
const renderedComponent = shallow(<RedditListPage {...props} />);
expect(renderedComponent.find('.examples-reddit-list-page').length).toBe(1);
expect(renderedComponent.find('.no-items-tip').length).toBe(1);
});
it("renders list items when there's data", () => {
const props = {
examples: { redditList: [{ data: { id: 'id', title: 'title', url: 'url' } }] },
actions: {},
};
const renderedComponent = shallow(<RedditListPage {...props} />);
expect(renderedComponent.find('.examples-reddit-list-page').length).toBe(1);
});
it('should disable fetch button when fetching reddit', () => {
const pageProps = {
examples: {
redditList: [],
fetchRedditListPending: true,
},
actions: {},
};
const renderedComponent = shallow(<RedditListPage {...pageProps} />);
expect(renderedComponent.find('.btn-fetch-reddit[disabled]').length).toBe(1);
});
it('should show error if fetch failed', () => {
const pageProps = {
examples: {
redditList: [],
fetchRedditListError: new Error('server error'),
},
actions: {},
};
const renderedComponent = shallow(<RedditListPage {...pageProps} />);
expect(renderedComponent.find('.fetch-list-error').length).toBe(1);
});
});

View File

@ -1,15 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { SidePanel } from '../../../src/features/examples/SidePanel';
describe('examples/SidePanel', () => {
it('renders node with correct class name', () => {
const props = {
examples: {},
actions: {},
};
const renderedComponent = shallow(<SidePanel {...props} />);
expect(renderedComponent.find('.examples-side-panel').length).toBe(1);
});
});

View File

@ -1,15 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { WelcomePage } from '../../../src/features/examples/WelcomePage';
describe('examples/WelcomePage', () => {
it('renders node with correct class name', () => {
const props = {
examples: {},
actions: {},
};
const renderedComponent = shallow(<WelcomePage {...props} />);
expect(renderedComponent.find('.examples-welcome-page').length).toBe(1);
});
});

View File

@ -1,28 +0,0 @@
import {
EXAMPLES_COUNTER_MINUS_ONE,
} from '../../../../src/features/examples/redux/constants';
import {
counterMinusOne,
reducer,
} from '../../../../src/features/examples/redux/counterMinusOne';
describe('examples/redux/counterMinusOne', () => {
it('returns correct action by counterMinusOne', () => {
expect(counterMinusOne()).toHaveProperty('type', EXAMPLES_COUNTER_MINUS_ONE);
});
it('handles action type EXAMPLES_COUNTER_MINUS_ONE correctly', () => {
const prevState = { count: 3 };
// TODO: use real expected state.
const expectedState = { count: 2 };
const state = reducer(
prevState,
{ type: EXAMPLES_COUNTER_MINUS_ONE }
);
// Should be immutable
expect(state).not.toBe(prevState);
expect(state).toEqual(expectedState);
});
});

View File

@ -1,25 +0,0 @@
import {
EXAMPLES_COUNTER_PLUS_ONE,
} from '../../../../src/features/examples/redux/constants';
import {
counterPlusOne,
reducer,
} from '../../../../src/features/examples/redux/counterPlusOne';
describe('examples/redux/counterPlusOne', () => {
it('returns correct action by counterPlusOne', () => {
expect(counterPlusOne()).toHaveProperty('type', EXAMPLES_COUNTER_PLUS_ONE);
});
it('handles action type EXAMPLES_COUNTER_PLUS_ONE correctly', () => {
const prevState = { count: 0 };
const expectedState = { count: 1 };
const state = reducer(
prevState,
{ type: EXAMPLES_COUNTER_PLUS_ONE }
);
expect(state).not.toBe(prevState); // should be immutable
expect(state).toEqual(expectedState); // TODO: replace this line with real case.
});
});

View File

@ -1,25 +0,0 @@
import {
EXAMPLES_COUNTER_RESET,
} from '../../../../src/features/examples/redux/constants';
import {
counterReset,
reducer,
} from '../../../../src/features/examples/redux/counterReset';
describe('examples/redux/counterReset', () => {
it('returns correct action by counterReset', () => {
expect(counterReset()).toHaveProperty('type', EXAMPLES_COUNTER_RESET);
});
it('handles action type EXAMPLES_COUNTER_RESET correctly', () => {
const prevState = { count: 10 };
const expectedState = { count: 0 };
const state = reducer(
prevState,
{ type: EXAMPLES_COUNTER_RESET }
);
expect(state).not.toBe(prevState); // should be immutable
expect(state).toEqual(expectedState); // TODO: replace this line with real case.
});
});

View File

@ -1,102 +0,0 @@
import _ from 'lodash';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import nock from 'nock';
import {
EXAMPLES_FETCH_REDDIT_LIST_BEGIN,
EXAMPLES_FETCH_REDDIT_LIST_SUCCESS,
EXAMPLES_FETCH_REDDIT_LIST_FAILURE,
EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR,
} from '../../../../src/features/examples/redux/constants';
import {
fetchRedditList,
dismissFetchRedditListError,
reducer,
} from '../../../../src/features/examples/redux/fetchRedditList';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('examples/redux/fetchRedditList', () => {
afterEach(() => {
nock.cleanAll();
});
it('dispatches success action when fetchRedditList succeeds', () => {
const list = _.times(2, i => ({
data: {
id: `id${i}`,
title: `test${i}`,
url: `http://example.com/test${i}`,
},
}));
nock('http://www.reddit.com/')
.get('/r/reactjs.json')
.reply(200, { data: { children: list } });
const store = mockStore({ redditReactjsList: [] });
return store.dispatch(fetchRedditList()).then(() => {
const actions = store.getActions();
expect(actions[0]).toHaveProperty('type', EXAMPLES_FETCH_REDDIT_LIST_BEGIN);
expect(actions[1]).toHaveProperty('type', EXAMPLES_FETCH_REDDIT_LIST_SUCCESS);
});
});
it('dispatches failure action when fetchRedditList fails', () => {
nock('http://www.reddit.com/')
.get('/r/reactjs.json')
.reply(500, null);
const store = mockStore({ redditReactjsList: [] });
return store.dispatch(fetchRedditList({ error: true })).catch(() => {
const actions = store.getActions();
expect(actions[0]).toHaveProperty('type', EXAMPLES_FETCH_REDDIT_LIST_BEGIN);
expect(actions[1]).toHaveProperty('type', EXAMPLES_FETCH_REDDIT_LIST_FAILURE);
expect(actions[1]).toHaveProperty('data.error', expect.anything());
});
});
it('returns correct action by dismissFetchRedditListError', () => {
const expectedAction = {
type: EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR,
};
expect(dismissFetchRedditListError()).toEqual(expectedAction);
});
it('handles action type EXAMPLES_FETCH_REDDIT_LIST_BEGIN correctly', () => {
const prevState = { fetchRedditListPending: false };
const state = reducer(prevState, { type: EXAMPLES_FETCH_REDDIT_LIST_BEGIN });
expect(state).not.toBe(prevState); // should be immutable
expect(state.fetchRedditListPending).toBe(true);
});
it('handles action type EXAMPLES_FETCH_REDDIT_LIST_SUCCESS correctly', () => {
const prevState = { fetchRedditListPending: true };
const state = reducer(prevState, {
type: EXAMPLES_FETCH_REDDIT_LIST_SUCCESS,
data: { data: { children: [] } },
});
expect(state).not.toBe(prevState); // should be immutable
expect(state.fetchRedditListPending).toBe(false);
});
it('handles action type EXAMPLES_FETCH_REDDIT_LIST_FAILURE correctly', () => {
const prevState = { fetchRedditListPending: true };
const state = reducer(prevState, {
type: EXAMPLES_FETCH_REDDIT_LIST_FAILURE,
data: { error: new Error('some error') },
});
expect(state).not.toBe(prevState); // should be immutable
expect(state.fetchRedditListPending).toBe(false);
expect(state.fetchRedditListError).toEqual(expect.anything());
});
it('handles action type EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR correctly', () => {
const prevState = { fetchRedditListError: new Error('some error') };
const state = reducer(prevState, { type: EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR });
expect(state).not.toBe(prevState); // should be immutable
expect(state.fetchRedditListError).toBe(null);
});
});

View File

@ -1,14 +0,0 @@
import reducer from '../../../../src/features/examples/redux/reducer';
describe('examples/redux/reducer', () => {
it('does nothing if no matched action', () => {
const prevState = {};
const state = reducer(
prevState,
{ type: '__UNKNOWN_ACTION_TYPE__' }
);
expect(state).toEqual(prevState);
});
// TODO: add global reducer test if needed.
});