Mid-Mod Notes
Time: 3 hrs
- Spec like IdeaBox
- Build out an app from a boilerplate / work through iterations
- Dependencies installed
- 2 repos (1 FE and 1 BE)
- Bring in data from an API
- Display data, build out a form
- Don’t worry about nested fetch requests
- Don’t worry about React Router
Review:
- Fetch (GET, POST, DELETE)
Testing:
- Snapshot
- Changes in state
- Testing methods
- Event simulation tests
- Extension: Async testing
You can use your notes and lessons, and google! You can’t look at previous code!
- In the terminal run
npx create-react-app NAMEOFYOURAPP
- Cd into the new directory:
cd NAMEOFYOURAPP
- Run
npm install
. - You can run
npm start
to see if the app was set up correctly.
Setting Up Testing
- Install enzyme:
npm i enzyme -D
- Install enzyme-adapter-react-16:
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-to-json
npm install enzyme-to-json -D
- In package.json, add the following lines of code:
"jest": {
"snapshotSerializers": [
"enzyme-to-json/serializer"
]
}
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. - Include the following lines as headers for all test files:
import React from 'react';
import { shallow } from 'enzyme';
import ClassName from './ClassName';
Class Component
import React, { Component } from 'react';
import Classname from './Classname';
import Funcname from '../Funcname/Funcname';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
key: value
}
}
method = () => {
console.log('run something')
}
render() {
return (
<div>
Return something
</div>
)
}
}
export default App;
Functional Component
import React from 'react';
import anotherFuncname from '../anotherFuncname/anotherFuncname';
import './Funcname.css';
const Funcname = () => {
return (
<div>
Return something
</div>
)
}
export default Funcname;
setState
.then(ideas => this.setState({
ideas: ideas
}))
.then(ideas => this.setState({ ideas }))
this.setState({
value: key
})
setState Is Async
functionName = (parameter) => {
this.setState({
value: key
}, () => {
anotherFunctionName((parameter) => {
return something
}))
});
};
Snapshot Test
import React from 'react';
import { shallow } from 'enzyme';
import NameOfTested from './NameOfTested';
describe('NameOfTested', () => {
it('should', () => {
const wrapper = shallow(
<Component />
)
expect(wrapper).toMatchSnapshot();
})
})
Test Dynamic Changes Template
Setup - What do we need to do in order to render the component (aka shallow or mount). What data needs to be mocked?
Execution - Let’s run the command or simulate the action.
Expectation - This is where our assertion happens. After running our function, what did we expect to happen?
Update State Test
import React from 'react';
import ReactDOM from 'react-dom';
import { shallow } from 'enzyme';
import NameOfTested from './NameOfTested';
describe('NameOfTested', () => {
it('should', () => {
const wrapper = shallow(
<Component />
)
const mockData = ?
const expected = ?
wrapper.instance().functionName(mockData);
expect(wrapper.state('stateKeyName')).toEqual(expected);
})
})
Function Test
it('should change state based on an event', () => {
const mockFunction = jest.fn()
const wrapper = shallow(<
Component
submitUser={mockFunction}
/>)
const mockEvent = {
target: {
name: 'name', value: 'value'
}
}
wrapper.instance().handleChange(mockEvent)
expect(wrapper.state('name')).toEqual('value')
})
Simulate A Click Test
wrapper.instance().forceUpdate();
wrapper.find('element').simulate('click');
wrapper.find('element').at(0).simulate('click');
wrapper.find('button').simulate('click', mockEvent);
expect(mockFunction).toHaveBeenCalledWith(argument);
expect(wrapper.instance().resetInputs).toHaveBeenCalled();
Ex:
it('should run funcName on click', () => {
const mockFuncName = jest.fn();
const wrapper = shallow(
<Component
prop={look to parent to see what gets passed down}
prop={look to parent to see what gets passed down}
/>
)
wrapper.find('element').at(0).simulate('click');
expect(mockFuncName).toHaveBeenCalled();
});
Mock A Function
const mockFunction = jest.fn();
Mock An Event
const mockEvent = {
preventDefault: jest.fn();
}
Mock Date.now()
global.Date.now = jest.fn().mockImplementation(() => 12345)
const expected = { title: '', description: '', id: 12345 };
Debug
console.log(wrapper.debug());
Verbose In package.json, under scripts, edit the "test" script:
"test": "react-scripts test --verbose"
Test Percentage Chart In Terminal
npm test -- --coverage --watchAll=false
Preparation
- Checkout your
master
branch and double-check to make sure it is up-to-date and there is nothing needed to be committed at this point - Navigate to the root of your project directory
- Open your project in your text editor
- Double-check that all image tags in your HTML have the
src
attribute have./
to start the path, e.gsrc="./images/turing-logo.png"
Required Steps
- In the
package.json
file, within the"repository"
object, edit the"url"
value to be"git+https://github.com/USERNAME/REPONAME.git"
where USERNAME and REPONAME are replaced with your GitHub username and your repository name, respectively
OR NOT?
- In the
package.json
file, add the line:"homepage": "http://USERNAME.github.io/REPONAME",
where USERNAME and REPONAME are replaced with your GitHub username and your repository name, respectively
DO THIS "homepage": ".",
- Add these two lines to the
scripts
section of thepackage.json
file:
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
- In the terminal, run
npm install --save-dev gh-pages
- You should see these lines of JSON added to your
package.json
file:
"devDependencies": {
"gh-pages": "^1.1.0"
}
- Run
npm run build
in the command line - Run
npm run deploy
in the command line
All of this will create a gh-pages
branch in your repo with the contents from the build
directory.
If you go to the GitHub pages site (http://USERNAME.github.io/REPONAME) in a minute, you should see your app! If not, check out the console to see what errors you're getting and troubleshoot from there.
Deploying with React Router
- npm install --save gh-pages
- “homepage”: “.“,
- “predeploy”: “npm run build”, “deploy”: “gh-pages -d build”
- import { HashRouter as Router } from ‘react-router-dom’ in
index.js
file and change to just<Router></Router>
- run npm run and build commands, done, go check GitHub Pages.
When You Make New Changes
If you make new changes to your master
branch, GitHub Pages doesn't automatically know about these changes, and your site won't be up-to-date. You need to update the gh-pages
branch to see those changes live on GitHub Pages. Here is how to update and keep everything in sync:
- After you're done making changes, checkout your
master
branch and double-check to make sure it is up-to-date and there is nothing needed to be committed at this point - Run
npm run build
in the command line - Run
npm run deploy
in the command line
componentDidMount() {
fetch('URL')
.then(response => response.json())
.then(data => this.setState({
key: data
}))
.catch(error => console.error(error))
}
addIdea = varName => {
const options = {
method: 'POST',
body: JSON.stringify(varName),
headers: {
'Content-Type': 'application/json'
}
};
fetch('URL', options)
.then(response => response.json())
.then(data => this.setState({
key: [...this.state.key, data]
}))
.catch(error => console.error(error))
}
removeIdea = id => {
const options = {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
}
fetch(`URL/${id}`, options)
.then(() => fetch('URL'))
.then(response => response.json())
.then(data => this.setState({
key: data
}))
.catch(error => console.error(error));
}
}
Functions needed
handleChange = event => {
this.setState({
[event.target.name]: event.target.value
})
}
submitNewData = event => {
event.preventDefault()
const newData = {
match key/value pairs of required data
}
this.props.funcFromApp(newData)
this.clearInputs()
}
clearInputs = () => {
this.setState({
key1: '',
key2: ''
})
}
<input
name=''
placeholder=''
value={this.state.key}
onChange={() => {}}
/>
Card.propTypes = {
title: PropTypes.string.isRequired
}
https://reactjs.org/docs/typechecking-with-proptypes.html#react.proptypes
git clone [https://github.com/ REPO NAME THEY GIVE US]
cd [CD INTO REPO]
npm install nodemon -g
npm i
npm start
Note that the frontend should be running on localhost:3000
and the backend should be running on localhost:3001
.
Clone down the backend repo - but NOT inside your existing frontend repository!
ideas: [...this.state.key, newVariableElement]
React I: The What and The Why https://frontend.turing.io/lessons/module-3/react-i.html
React II: The How, building IdeaBox https://frontend.turing.io/lessons/module-3/react-ii.html
React III: Workshop Ideabox With A Backend https://frontend.turing.io/lessons/module-3/react-iii.html
Unit Testing React Components https://frontend.turing.io/lessons/module-3/unit-testing-react.html
Get Your Site On GH Pages https://github.com/turingschool-examples/webpack-starter-kit/blob/master/gh-pages-procedure.md
Webpack Starter Kit https://github.com/turingschool-examples/webpack-starter-kit
Network Request GET/POST Requests https://frontend.turing.io/lessons/module-3/network-request-exercises.html
All Lessons https://frontend.turing.io/lessons/
Setting Up ESLint
- 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
Install SCSS/Sass
- 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
- Remeber 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;
Any time .toHaveBeenCalledWIth()
is called it needs to be used with a mock function so it can be spied on.
apiCalls testing
describe('apiCall', () => {
const url = 'http://localhost:3001/api/v1/users';
const newUser = {
name: 'Pol',
email: 'email@email.com',
password: 'password',
};
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newUser)
};
const mockResponse = {
type: 'cors',
url: 'http://localhost:3001/api/v1/users',
redirected: false,
status: 201,
ok: true
};
it('should call fetch with specified url', () => {
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve(mockResponse)
});
});
attemptCreateUser(newUser);
expect(window.fetch).toHaveBeenCalledWith(url, options);
});
it('should return error if response not okay', () => {
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: false
});
});
expect(attemptCreateUser(newUser)).rejects.toEqual(
Error('That username is already associated with another account. Try another email address.')
);
});
it('should return an error if the server is down', () => {
window.fetch = jest.fn().mockImplementation(() => {
return Promise.reject(Error('500 Internal Server Error'));
});
expect(attemptCreateUser(newUser)).rejects.toEqual(Error('500 Internal Server Error'));
});
});
describe('attemptLoginUser', () => {
const url = 'http://localhost:3001/api/v1/login';
const user = {
email: 'email@email.com',
password: 'password',
};
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(user)
};
const mockResponse = {
type: 'cors',
url: 'http://localhost:3001/api/v1/login',
redirected: false,
status: 201,
ok: true
};
it('should call fetch with specified url', () => {
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve(mockResponse)
});
});
attemptLoginUser(user);
expect(window.fetch).toHaveBeenCalledWith(url, options);
});
it('should return error if response not okay', () => {
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: false
});
});
expect(attemptLoginUser(user)).rejects.toEqual(
Error('Email does not exist or password is incorrect')
);
});
it('should return an error if the server is down', () => {
window.fetch = jest.fn().mockImplementation(() => {
return Promise.reject(Error('500 Internal Server Error'));
});
expect(attemptLoginUser(user)).rejects.toEqual(Error('500 Internal Server Error'));
});
});```