- In the terminal run:
npx create-react-app NAME-OF-APP
- Cd into the new directory:
cd NAME-OF-APP
- Run:
npm install
. - You can run
npm start
to see if the app was set up correctly.
npm i redux react-redux redux-devtools-extension -S
- redux - Allows us to have access to using Redux in our app.
- react-redux - Allows us to connect our react components to our Redux store.
- redux-devtools-extension - Useful for debugging in our devtools
- In src/index.js
import { Provider } from 'react-redux';
- a component from react-redux that wraps our App component and allows each child component to be connected to the store
import { createStore } from 'redux';
- a function from Redux that uses the rootReducer to create the store
import { composeWithDevTools } from 'redux-devtools-extension';
- a method we brought in and can pass as an argument with createStore so that we have access to our devtools and can view our store.
import { rootReducer } from './reducers';
- our combined reducers to create our store
(order matters here)
const store = createStore(rootReducer, composeWithDevTools());
ReactDOM.render(
<Provider store={store} >
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
- In actions/index.js
export const addToDo = todo => ({
type: 'ADD_TODO',
todo
})
- In src/reducers/index.js
import { combineReducers } from 'redux';
import { todos } from './todos';
export const rootReducer = combineReducers({
todos: todos
});
- Connecting components to the Redux store:
import { connect } from 'react-redux';
mapState example:
export const mapState = state => ({
intentions: state.intentions
})
export default connect(mapState)(IntentionsContainer)
mapDispatch example:
export const mapDispatch = dispatch => ({
postIntention: intention => dispatch(postIntention(intention))
})
export default connect(null, mapDispatch)(Form);
**Where we define the properties that will exsits in our global store
npm i redux-thunk -S
- Clone repo, not nested in project directory
- Globally install nodemon. Runs the server.
npm install nodemon -g
- cd into repo
- Run
npm install
- Run
npm start
- Use Postman to checkout the data
- Install Enzyme:
npm i enzyme -D
- Install Enzyme Adapter:
npm install enzyme-adapter-react-16 -D
- Inside of /src create setupTests.js:
touch src/setupTests.js
- Put the following 3 lines of code in setupTests.js
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
- For snapshot testing, install Enzyme Wrappers:
npm install enzyme-to-json --save-dev
- Add serializer to package.json, after devDependencies:
"jest": {
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"coveragePathIgnorePatterns": [
"src/index.js",
"src/serviceWorker.js"
]
}
Don't forget the comma!
- Get those sweet, sweet checkmarks in your tests - edit scripts line in package.json:
"test": "react-scripts test --verbose"
Don't forget the comma!
- Add an extra line in App.js (just so there's a change in the file) and save. Then run
npm test
to check that the files are connected correctly. If you get an error about fsEvents not being a function, run the following commands in terminal:
npm audit fix --force
Run npm test to see if that worked. If not, run:
brew install watchman
- Include the following lines as headers for all test files:
import React from 'react';
import { shallow } from 'enzyme';
import ClassName from './ClassName';
npm test -- --coverage --watchAll=false
- ESLint is already built in with create-react-app. Installing another eslint will likely break things.
- Add a script called
"lint": "eslint src/" in your package.json
(in the scripts object) - In your terminal, run:
npm run lint
- Turing Mod3 Linter
- Run:
npm install node-sass --save
- Add variable file:
touch src/variables.scss
- Change all
.css
file extentions to.scss
index.css App.css
=>index.scss App.scss
- Remember to update the file paths anywhere the style files are being imported
- Add:
@import './src/variables.scss';
To any .scss files you want to uses variables - Format for creating variables:
$var-name: var-value;
- @mixins:
touch src/mixins.scss
- create scss directory and move scss files (just variables and mixins) to the scss directory.
- Rounded corners:
@mixin rounded-corners($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
border-radius: $radius;
}
npm install prop-types -S
import PropTypes from 'prop-types'
Component.propTypes = {
prop1: PropTypes.array.isRequired,
prop2: PropTypes.any,
prop3: PropTypes.func.isRequired,
prop4: PropTypes.bool
prop5: PropTypes.string.isRequired,
prop6: PropTypes.number
}
npm i react-router-dom -S
- In index.js
import { BrowserRouter } from 'react-router-dom'
const router = (
<BrowserRouter>
<App />
</BrowserRouter>
)
ReactDOM.render(router, document.getElementById('root'));
- Import what is needed in components
import { Route, NavLink, Link, Redirect, Switch} from 'react-router-dom'
import { shallow } from 'enzyme';
wrapper = shallow (<Component prop='string' prop2={2} method={jest.fn()} />
expect(wrapper).toMatchSnapshot()
Wrapper with Promise/Fetch GET Example:
- Make sure to declare variables outside of beforeEach so you have access to them in it blocks.
- getOrders is a fetch, so we use mockImplementation to return a resolved promise with whatever we get back.
- Pass through whatever we're going to need as props (orders and mockSetOrders)
describe('Orders', () => {
let wrapper, mockSetOrders, mockOrders;
beforeEach(() => {
mockOrders = [{ id: 1, name: 'Wild Ben', ingredients: ['beans', 'beans', 'steak']}]
mockSetOrders = jest.fn();
getOrders.mockImplementation(() => {
return Promise.resolve({
orders: [{ id: 1, name: 'Wild Ben', ingredients: ['beans', 'beans', 'steak']}]
})
});
wrapper = shallow(<Orders
orders={mockOrders}
setOrders={mockSetOrders}
/>)
});
it('should match snapshot when orders have been placed', () => {
expect(wrapper).toMatchSnapshot();
});
Execution
wrapper.instance().methodName('optionalArgument')
wrapper.instance().forceUpdate()
wrapper.find('button').simulate('click', optionalEvent)
wrapper.instance().setState({ prop: 'value', prop2: 'value2' })
wrapper.find('[name='thing']').simulate('change', mockThingEvent)
wrapper.find('button').at(3).simulate('click')
expect(mockWhatEverEvent).toHaveBeenCalledWith('argument/value')
Mock Date.now()
global.Date.now = jest.spyOn(global.Date, 'now').mockImplementation(() => 123)
Can assert that the value of Date.now() will be 123 or whatever is set as the return value.
Mock e.preventDefault() Can be passed as arugments of event in other methods
const mockEvent = { preventDefault: jest.fn() }
Mock Event
const mockThingEvent = { target: { name: 'thing', value: 'Thing value' } }
e.g.
describe('Form', () => {
let wrapper, mockEvent, mockCurrentUser, mockSetLocation, mockSetSpookyLocations;
describe('Form Component', () => {
beforeEach(() => {
mockEvent = { target: { name: 'username', value: 'KWoo'}}
mockCurrentUser = jest.fn();
mockSetLocation = jest.fn();
mockSetSpookyLocations = jest.fn();
wrapper = shallow(<Form
updateUser={mockCurrentUser}
setLocation={mockSetLocation}
setSpookyLocations={mockSetSpookyLocations}
/>)
})
it('should match the snapshot when there is no error in state', () => {
expect(wrapper).toMatchSnapshot();
})
Expectation
expect(wrapper.instance().handleChange).toHaveBeenCalledWith(mockThingEvent)
expect(wrapper.state('thing')).toEqual('Thing value') or expect(wrapper.state()).toEqual(expected)
expect(wrapper.instance().method).toHaveBeenCalled()
(no imports)
export const setOrders = orders => ({
type: 'SET_ORDERS',
orders
});
import * as actions from './actions';
describe('action', () => {
it('should update the current user', () => {
const user = 'KWoo';
const result = actions.currentUser(user);
const mockExpectedAction = {
type: 'SET_USER',
name: user
}
expect(result).toEqual(mockExpectedAction);
})
or this:
it('should update the user location', () => {
const location = 'Denver, CO';
const result = actions.setLocation(location);
const mockExpectedAction = {
type: 'SET_LOCATION',
location
}
expect(result).toEqual(mockExpectedAction);
})
(no imports)
export const orders = (state = [], action) => {
switch (action.type) {
case 'SET_ORDERS':
return action.orders;
default:
return state;
}
};
Reducer (adding an object to an array)
export const updateFavorites = (state = [], action) => {
switch (action.type) {
case 'UPDATE_FAVORITES':
return [...state, action.location]
case 'REMOVE_FAVORITE':
return state.filter(favorite => favorite.location !== action.location.location)
default:
return state
}
}
And corresponding test: Remember to test default state and then what expecting after passing in action object
import { updateFavorites } from '../reducers/updateFavorites';
describe('updateFavorites reducer', () => {
it('should return initial state', () => {
const expected = [];
const result = updateFavorites(undefined, []);
expect(result).toEqual(expected)
})
it('should return the correct state if the action is UPDATE_FAVORITES', () => {
const initialState = [];
const action = {
type (something extra - could be annything): 'UPDATE_FAVORITES',
location: {
"city": "Denver",
"country": "United States",
"description": "BOO!",
"location": "Ada Cemetery",
"state": "Colorado",
"state_abbrev": "CO",
"longitude": -85.50489309999999,
"latitude": 42.9621061,
"city_longitude": -85.4954803,
"city_latitude": 42.960727
}
};
const result = updateFavorites(initialState, action);
const expected = [...initialState, action.location];
expect(result).toEqual(expected)
})
})
What to import (e.g. for Header Component)
import React from 'react';
import Header from './Header';
import { shallow } from 'enzyme';
describe('mapStateToProps', () => {
it('should return an object with orders', () => {
const mockState = {
orders: [{
id: 1,
name: 'Wild Ben',
ingredients: ['beans', 'beans', 'steak']
}],
extra: 'extraInfo'
};
const expected = {
orders: [{
id: 1,
name: 'Wild Ben',
ingredients: ['beans', 'beans', 'steak']
}]
};
const mappedProps = mapStateToProps(mockState);
expect(mappedProps).toEqual(expected);
})
})
another example:
it('should call mapDispatchToProps with a location when the user enters a location', () => {
const mockMapDispatchToProps = jest.fn();
const actionToDispatch = setLocation('Denver, CO');
const mockProps = mapDispatchToProps(mockMapDispatchToProps);
mockProps.setLocation('Denver, CO');
expect(mockMapDispatchToProps).toHaveBeenCalledWith(actionToDispatch);
})
describe('mapDispatchToProps', () => {
it('should call mapDispatchToProps with a setOrders action when setOrders is called from props', () => {
const mockDispatchToProps = jest.fn();
const actionToDispatch = setOrders([{ id: 1, name: 'Wild Ben', ingredients: ['beans', 'beans', 'steak']}]);
const mappedProps = mapDispatchToProps(mockDispatchToProps);
mappedProps.setOrders([{ id: 1, name: 'Wild Ben', ingredients: ['beans', 'beans', 'steak']}]);
expect(mockDispatchToProps).toHaveBeenCalledWith(actionToDispatch);
})
- import combineReducers
- import all reducers from their files
- Remember, this is where you are setting global state, so you can change the key/value pairs here
import { combineReducers } from 'redux';
import { currentUser } from './currentUser';
import { location } from './setLocation';
import { spookyLocations } from './spookyLocations';
import { updateFavorites } from './updateFavorites';
import { clickedCard } from './clickedCard';
const rootReducer = combineReducers({
currentUser,
location,
spookyLocations,
favorites: updateFavorites,
currentMap: clickedCard
})
export default rootReducer;
Import:
- React
- ReactDOM
- styling
- App
- Provider
- createStore
- composeWithDevTools
- rootReducer from file
- BrowserRouter
Example:
import React from 'react';
import ReactDOM from 'react-dom';
import './scss/index.scss';
import { App } from './Components/App/App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './reducers/reducers';
import { BrowserRouter } from 'react-router-dom';
const store = createStore(rootReducer, composeWithDevTools())
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>,
</Provider>,
document.getElementById('root')
);
Import:
- React, { Component } from 'react'
- styling
- Any components that will be rendered
Example:
export class App extends Component {
render() {
return (
<>
<Route path="/" component={Header}/>
<Switch>
<Route exact path="/" component={Form}/>
<Route path="/locations" component={LocationContainer}/>
<Route path="/favorites" component={FavoritesContainer}/>
<Route path="/location" component={MapContainer}/>
<Route component={NoURLMatch}/>
</Switch>
</>
)
}
}
export default App;
Import:
- React from 'react'
- styling
Example:
import React from 'react';
import './NoURLMatch.scss';
export const NoURLMatch = () => {
return (
<main className="no-url-container">
<h2 className="no-url-match-message">This page is a dead end. Please return home.</h2>
</main>
)
}
export default NoURLMatch;
POST
const options = {
method: 'POST',
body: JSON.stringify({
id: value,
prop: value,
}),
headers: {
'Content-Type': 'application/json'
}
}
return fetch('url', options)
.then(res => {
if(!res.ok) {
throw Error('Something is not right, try again later')
}
return res.json()})
DELETE
const options = {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
}
return fetch(`url/${id}`, options)
.then(res => {
if(!res.ok) {
throw Error('Something is not right, try again later')
}
return res.json()
}).catch(error => {
throw Error(error.message)
});
handleChange = e => {
this.setState({ [e.target.name]: e.target.value})
}
import { getIdeas, postIdea } from './apiCalls'
// no need to import react
// no need to import enzyme
describe('apiCalls', () => {
describe('getIdeas', () => {
let mockResponse = [
{
id: 1,
title: "Sweaters for pugs",
description: "To keep them warm"
}
]
beforeEach(() => {
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => {
return Promise.resolve(mockResponse)
}
})
})
})
it('should be passed the correct URL', () => {
getIdeas()
expect(window.fetch).toHaveBeenCalledWith('http://localhost:3001/api/v1/ideas')
})
it('should return an array of ideas', () => {
expect(getIdeas()).resolves.toEqual(mockResponse)
})
it('should return an error for response that is !ok', () => {
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: false
})
})
expect(getIdeas()).rejects.toEqual(Error('Error fetching ideas'))
})
})
- Make new action object in mapDispatch
removeFavorite: location => dispatch(removeFavorite(location))
- Make new action object in actions.js
export const removeFavorite = location => ({
type: 'REMOVE_FAVORITE',
location
})
- Update reducer to include path for this action object
export const updateFavorites = (state = [], action) => {
switch (action.type) {
case 'UPDATE_FAVORITES':
return [...state, action.location]
case 'REMOVE_FAVORITE':
return state.filter(favorite => favorite.location !== action.location.location)
default:
return state
}
}