From 89ba627365fed750435b3b7658618005c881b2ab Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Tue, 24 Sep 2019 01:13:02 +0200 Subject: [PATCH] add unit tests --- .babelrc | 12 ++++- .gitignore | 1 + README.md | 1 + package.json | 31 +++++++---- src/renderer/App.jsx | 32 ++++++----- src/renderer/App.test.jsx | 15 ++++++ src/renderer/components/Animations.js | 12 ----- src/renderer/jest.config.js | 13 +++++ src/renderer/jest/__fixtures__/context.js | 34 ++++++++++++ src/renderer/jest/__mocks__/file-mock.js | 1 + src/renderer/jest/__mocks__/svgr-mock.js | 1 + src/renderer/jest/setup-test-env.js | 1 + .../{components => screens/Home}/Welcome.jsx | 4 +- .../Home}/Welcome.module.css | 0 src/renderer/screens/Home/index.jsx | 6 +-- src/renderer/screens/Home/index.test.jsx | 28 ++++++++++ .../screens/Preferences/Accounts/New.jsx | 45 +++++++++------- .../screens/Preferences/Accounts/Saved.jsx | 54 ++++++++++--------- .../Preferences/Accounts/index.test.jsx | 50 +++++++++++++++++ .../screens/Preferences/index.test.jsx | 15 ++++++ 20 files changed, 264 insertions(+), 92 deletions(-) create mode 100644 src/renderer/App.test.jsx create mode 100644 src/renderer/jest.config.js create mode 100644 src/renderer/jest/__fixtures__/context.js create mode 100644 src/renderer/jest/__mocks__/file-mock.js create mode 100644 src/renderer/jest/__mocks__/svgr-mock.js create mode 100644 src/renderer/jest/setup-test-env.js rename src/renderer/{components => screens/Home}/Welcome.jsx (80%) rename src/renderer/{components => screens/Home}/Welcome.module.css (100%) create mode 100644 src/renderer/screens/Home/index.test.jsx create mode 100644 src/renderer/screens/Preferences/Accounts/index.test.jsx create mode 100644 src/renderer/screens/Preferences/index.test.jsx diff --git a/.babelrc b/.babelrc index a3d157c..d5a26dc 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,14 @@ { - "presets": ["@babel/env", "@babel/react"], + "presets": [ + [ + "@babel/env", + { + "targets": { + "node": "current" + } + } + ], + "@babel/react" + ], "plugins": ["@babel/plugin-proposal-class-properties"] } diff --git a/.gitignore b/.gitignore index 03f0caf..0a06796 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ yarn.lock package-lock.json build dist +coverage diff --git a/README.md b/README.md index a4352bd..5c85f16 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@

+

diff --git a/package.json b/package.json index d6ecb81..4a9d5fc 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "description": "🐡 Simple Electron-based desktop app to retrieve and display your total Ocean Token balances.", "main": "./src/main/index.js", "scripts": { - "test": "eslint --ignore-path .gitignore ./src/**/*.{js,jsx} && stylelint --ignore-path .gitignore ./src/**/*.{css,scss}", + "test": "npm run lint && jest --config=src/renderer/jest.config.js --coverage", + "test:watch": "jest --config=src/renderer/jest.config.js --coverage --watch", + "lint": "eslint --ignore-path .gitignore ./src/**/*.{js,jsx} && stylelint --ignore-path .gitignore ./src/**/*.{css,scss}", "start": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js", "build": "cross-env NODE_ENV=production webpack --config webpack.common.config.js", "package": "electron-builder build -mwl -p never && open ./dist", @@ -30,41 +32,48 @@ "ms": "^2.1.2" }, "devDependencies": { - "@babel/core": "^7.6.0", + "@babel/core": "^7.6.2", "@babel/plugin-proposal-class-properties": "^7.5.5", - "@babel/preset-env": "^7.6.0", + "@babel/preset-env": "^7.6.2", "@babel/preset-react": "^7.0.0", + "@jest-runner/electron": "^2.0.2", "@reach/router": "^1.2.1", + "@react-mock/state": "^0.1.8", "@svgr/webpack": "^4.3.2", + "@testing-library/jest-dom": "^4.1.0", + "@testing-library/react": "^9.1.4", "auto-changelog": "^1.16.1", "babel-eslint": "^10.0.3", + "babel-jest": "^24.9.0", "babel-loader": "^8.0.6", "copy-webpack-plugin": "^5.0.4", "cross-env": "^6.0.0", "css-loader": "^3.2.0", - "electron": "^6.0.7", + "electron": "^6.0.10", "electron-builder": "^21.2.0", "electron-devtools-installer": "^2.2.4", "electron-store": "^5.0.0", - "eslint": "^6.3.0", - "eslint-config-prettier": "^6.2.0", + "eslint": "^6.4.0", + "eslint-config-prettier": "^6.3.0", "eslint-plugin-react": "^7.14.3", "file-loader": "^4.2.0", "html-webpack-plugin": "^3.2.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^24.9.0", "mini-css-extract-plugin": "^0.8.0", "prettier": "^1.18.2", "prettier-stylelint": "^0.4.2", "react": "^16.9.0", "react-dom": "^16.9.0", "react-pose": "^4.0.8", - "release-it": "^12.3.6", + "release-it": "^12.4.1", "style-loader": "^1.0.0", "stylelint": "^11.0.0", - "stylelint-config-css-modules": "^1.4.0", + "stylelint-config-css-modules": "^1.5.0", "stylelint-config-standard": "^19.0.0", - "webpack": "^4.39.3", - "webpack-cli": "^3.3.8", - "webpack-dev-server": "^3.8.0" + "webpack": "^4.40.2", + "webpack-cli": "^3.3.9", + "webpack-dev-server": "^3.8.1" }, "browserslist": "electron >= 6.0", "build": { diff --git a/src/renderer/App.jsx b/src/renderer/App.jsx index 6454683..a1c75af 100644 --- a/src/renderer/App.jsx +++ b/src/renderer/App.jsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react' +import React, { useEffect } from 'react' import PropTypes from 'prop-types' import { Router, @@ -44,24 +44,22 @@ PosedRouter.propTypes = { children: PropTypes.any.isRequired } -export default class App extends PureComponent { - componentDidMount() { +export default function App() { + useEffect(() => { ipcRenderer.on('goTo', (evt, route) => { navigate(route) }) - } + }, []) - render() { - return ( - <> - {process.platform === 'darwin' && } -
- - - - -
- - ) - } + return ( + <> + {process.platform === 'darwin' && } +
+ + + + +
+ + ) } diff --git a/src/renderer/App.test.jsx b/src/renderer/App.test.jsx new file mode 100644 index 0000000..7145615 --- /dev/null +++ b/src/renderer/App.test.jsx @@ -0,0 +1,15 @@ +import React from 'react' +import { render } from '@testing-library/react' +import AppProvider from './store/AppProvider' +import App from './App' + +describe('App', () => { + it('renders correctly', () => { + const { container } = render( + + + + ) + expect(container.firstChild).toBeInTheDocument() + }) +}) diff --git a/src/renderer/components/Animations.js b/src/renderer/components/Animations.js index b6ae698..e16c5f5 100644 --- a/src/renderer/components/Animations.js +++ b/src/renderer/components/Animations.js @@ -22,15 +22,3 @@ export const fadeIn = { transition: { duration: 100 } } } - -export const characterAnimation = { - exit: { opacity: 0, y: 10 }, - enter: { - opacity: 1, - y: 0, - transition: ({ charInWordIndex }) => ({ - type: 'spring', - delay: charInWordIndex * 20 - }) - } -} diff --git a/src/renderer/jest.config.js b/src/renderer/jest.config.js new file mode 100644 index 0000000..c5aa35d --- /dev/null +++ b/src/renderer/jest.config.js @@ -0,0 +1,13 @@ +module.exports = { + moduleNameMapper: { + '.+\\.(css|styl|less|sass|scss)$': 'identity-obj-proxy', + '.+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/jest/__mocks__/file-mock.js', + '\\.svg': '/jest/__mocks__/svgr-mock.js' + }, + testPathIgnorePatterns: ['node_modules', 'dist', 'build', 'coverage'], + testURL: 'http://localhost', + setupFilesAfterEnv: ['/jest/setup-test-env.js'], + runner: '@jest-runner/electron', + testEnvironment: '@jest-runner/electron/environment' +} diff --git a/src/renderer/jest/__fixtures__/context.js b/src/renderer/jest/__fixtures__/context.js new file mode 100644 index 0000000..77049b6 --- /dev/null +++ b/src/renderer/jest/__fixtures__/context.js @@ -0,0 +1,34 @@ +const prices = new Map() +prices.set('ocean', 1) +prices.set('eur', 1) +prices.set('usd', 1) +prices.set('btc', 1) +prices.set('eth', 1) + +const priceChanges = { + eur: -14.051056318029353, + usd: -14.051056318029268, + btc: -14.761248039421442, + eth: -17.538786176215627 +} + +export default { + accentColor: '#0a5fff', + accounts: [ + { + address: '0xxxxxxxxxxxxxxxxxxx', + balance: { + ocean: 10000, + btc: 1.9, + eth: 10000, + eur: 10000, + usd: 10000 + } + } + ], + currency: 'ocean', + isLoading: false, + needsConfig: false, + prices, + priceChanges +} diff --git a/src/renderer/jest/__mocks__/file-mock.js b/src/renderer/jest/__mocks__/file-mock.js new file mode 100644 index 0000000..6f60e34 --- /dev/null +++ b/src/renderer/jest/__mocks__/file-mock.js @@ -0,0 +1 @@ +module.exports = 'div' diff --git a/src/renderer/jest/__mocks__/svgr-mock.js b/src/renderer/jest/__mocks__/svgr-mock.js new file mode 100644 index 0000000..5a0f236 --- /dev/null +++ b/src/renderer/jest/__mocks__/svgr-mock.js @@ -0,0 +1 @@ +module.exports = 'svg-mock' diff --git a/src/renderer/jest/setup-test-env.js b/src/renderer/jest/setup-test-env.js new file mode 100644 index 0000000..264828a --- /dev/null +++ b/src/renderer/jest/setup-test-env.js @@ -0,0 +1 @@ +import '@testing-library/jest-dom/extend-expect' diff --git a/src/renderer/components/Welcome.jsx b/src/renderer/screens/Home/Welcome.jsx similarity index 80% rename from src/renderer/components/Welcome.jsx rename to src/renderer/screens/Home/Welcome.jsx index 648b3c1..bf26368 100644 --- a/src/renderer/components/Welcome.jsx +++ b/src/renderer/screens/Home/Welcome.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react' import { Link } from '@reach/router' -import { AppContext } from '../store/createContext' -import IconRocket from '../images/rocket.svg' +import { AppContext } from '../../store/createContext' +import IconRocket from '../../images/rocket.svg' import styles from './Welcome.module.css' const Welcome = () => { diff --git a/src/renderer/components/Welcome.module.css b/src/renderer/screens/Home/Welcome.module.css similarity index 100% rename from src/renderer/components/Welcome.module.css rename to src/renderer/screens/Home/Welcome.module.css diff --git a/src/renderer/screens/Home/index.jsx b/src/renderer/screens/Home/index.jsx index 5bd4259..5927904 100644 --- a/src/renderer/screens/Home/index.jsx +++ b/src/renderer/screens/Home/index.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react' import { Link } from '@reach/router' import { AppContext } from '../../store/createContext' -import Welcome from '../../components/Welcome' +import Welcome from './Welcome' import Spinner from '../../components/Spinner' import Divider from '../../components/Divider' import Total from './Total' @@ -10,7 +10,7 @@ import Accounts from './Accounts' import IconCog from '../../images/cog.svg' import styles from './index.module.css' -const Home = () => { +export default function Home() { const { isLoading, needsConfig } = useContext(AppContext) return ( @@ -37,5 +37,3 @@ const Home = () => { ) } - -export default Home diff --git a/src/renderer/screens/Home/index.test.jsx b/src/renderer/screens/Home/index.test.jsx new file mode 100644 index 0000000..82bbcd3 --- /dev/null +++ b/src/renderer/screens/Home/index.test.jsx @@ -0,0 +1,28 @@ +import React from 'react' +import { render, fireEvent } from '@testing-library/react' +import { AppContext } from '../../store/createContext' +import context from '../../jest/__fixtures__/context' +import Home from '.' + +describe('Home', () => { + it('renders correctly', () => { + const { container, getByText } = render( + + + + ) + expect(container.firstChild).toBeInTheDocument() + fireEvent.click(getByText(/Ξ/)) + }) + + it('renders Welcome without config', () => { + const { container } = render( + + + + ) + expect(container.firstChild).toHaveTextContent( + 'Add your first address to get started.' + ) + }) +}) diff --git a/src/renderer/screens/Preferences/Accounts/New.jsx b/src/renderer/screens/Preferences/Accounts/New.jsx index c72a0a8..6e09acf 100644 --- a/src/renderer/screens/Preferences/Accounts/New.jsx +++ b/src/renderer/screens/Preferences/Accounts/New.jsx @@ -2,25 +2,32 @@ import React from 'react' import PropTypes from 'prop-types' import styles from './New.module.css' -const New = ({ input, handleInputChange, handleSave, accentColor }) => ( -
  • - handleInputChange(e)} - className={styles.input} - /> +export default function New({ + input, + handleInputChange, + handleSave, + accentColor +}) { + return ( +
  • + handleInputChange(e)} + className={styles.input} + /> - -
  • -) + + + ) +} New.propTypes = { input: PropTypes.string.isRequired, @@ -28,5 +35,3 @@ New.propTypes = { handleSave: PropTypes.func.isRequired, accentColor: PropTypes.string.isRequired } - -export default New diff --git a/src/renderer/screens/Preferences/Accounts/Saved.jsx b/src/renderer/screens/Preferences/Accounts/Saved.jsx index 774e949..a298c7a 100644 --- a/src/renderer/screens/Preferences/Accounts/Saved.jsx +++ b/src/renderer/screens/Preferences/Accounts/Saved.jsx @@ -5,36 +5,40 @@ import posed, { PoseGroup } from 'react-pose' import { fadeIn } from '../../../components/Animations' import styles from './Saved.module.css' -const Item = posed.li(fadeIn) +export default function Saved({ accounts, handleDelete }) { + const Item = posed.li(fadeIn) -const Saved = ({ accounts, handleDelete }) => ( - - {accounts.map(account => { - const identicon = account && toDataUrl(account) + return ( + + {accounts.map(account => { + const identicon = toDataUrl(account) - return ( - -
    - Blockies - {account} -
    + return ( + +
    + Blockies + {account} +
    - -
    - ) - })} -
    -) + + + ) + })} +
    + ) +} Saved.propTypes = { accounts: PropTypes.array.isRequired, handleDelete: PropTypes.func.isRequired } - -export default Saved diff --git a/src/renderer/screens/Preferences/Accounts/index.test.jsx b/src/renderer/screens/Preferences/Accounts/index.test.jsx new file mode 100644 index 0000000..dc366c1 --- /dev/null +++ b/src/renderer/screens/Preferences/Accounts/index.test.jsx @@ -0,0 +1,50 @@ +import React from 'react' +import { render, fireEvent } from '@testing-library/react' +import { AppContext } from '../../../store/createContext' +import { StateMock } from '@react-mock/state' +import Accounts from '.' + +describe('Accounts', () => { + const ui = ( + + + + + + ) + + it('renders correctly', () => { + const { container } = render(ui) + expect(container.firstChild).toBeInTheDocument() + }) + + it('Address can be removed', () => { + const { getByTitle } = render(ui) + fireEvent.click(getByTitle('Remove account')) + }) + + it('New address can be added', () => { + const { getByPlaceholderText, getByText } = render(ui) + + // error: empty + fireEvent.click(getByText('Add')) + + // success + fireEvent.change(getByPlaceholderText('0xxxxxxxx'), { + target: { value: '0x139361162Fb034fa021d347848F0B1c593D1f53C' } + }) + fireEvent.click(getByText('Add')) + + // error: duplicate + fireEvent.change(getByPlaceholderText('0xxxxxxxx'), { + target: { value: '0x139361162Fb034fa021d347848F0B1c593D1f53C' } + }) + fireEvent.click(getByText('Add')) + + // error: not an ETH address + fireEvent.change(getByPlaceholderText('0xxxxxxxx'), { + target: { value: '0x000' } + }) + fireEvent.click(getByText('Add')) + }) +}) diff --git a/src/renderer/screens/Preferences/index.test.jsx b/src/renderer/screens/Preferences/index.test.jsx new file mode 100644 index 0000000..43d0ebf --- /dev/null +++ b/src/renderer/screens/Preferences/index.test.jsx @@ -0,0 +1,15 @@ +import React from 'react' +import { render } from '@testing-library/react' +import { AppContext } from '../../store/createContext' +import Preferences from '.' + +describe('Preferences', () => { + it('renders correctly', () => { + const { container } = render( + + + + ) + expect(container.firstChild).toBeInTheDocument() + }) +})