diff --git a/.eslintrc b/.eslintrc
index 203d3501..fc16e217 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -17,7 +17,8 @@
"env": {
"browser": true,
"node": true,
- "es6": true
+ "es6": true,
+ "jest": true
},
"rules": {
"quotes": ["error", "single"],
diff --git a/.gitignore b/.gitignore
index 546b06fa..bc2f2b08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ public
.cache
src/components/svg
plugins/gatsby-redirect-from
+coverage
diff --git a/.travis.yml b/.travis.yml
index c87312df..0e71dafe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,10 +18,19 @@ before_install:
- sudo apt-get install python3-pip
- sudo -H pip3 install --upgrade pip
+before_script:
+ # https://docs.codeclimate.com/docs/travis-ci-test-coverage
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
+ - chmod +x ./cc-test-reporter
+ - ./cc-test-reporter before-build
+
script:
- npm test
- travis_wait 60 npm run build
+after_script:
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
+
after_success:
- pip install --user awscli
- export PATH=$PATH:$HOME/.local/bin
diff --git a/README.md b/README.md
index c9c3a276..38c80250 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@
+
@@ -28,6 +29,7 @@
- [🍬 Typekit component](#-typekit-component)
- [✨ Development](#-development)
- [🔮 Linting](#-linting)
+ - [👩🔬 Testing](#-testing)
- [🎈 Add a new post](#-add-a-new-post)
- [🚚 Deployment](#-deployment)
- [🏛 Licenses](#-licenses)
@@ -139,7 +141,6 @@ All SVG assets under `src/images/` will be converted to React components with th
```jsx
import { ReactComponent as Logo } from './components/svg/Logo'
-
;
```
@@ -185,6 +186,24 @@ npm run format
npm run format:css
```
+### 👩🔬 Testing
+
+Test suite is setup with [Jest](https://jestjs.io) and [react-testing-library](https://github.com/kentcdodds/react-testing-library).
+
+To run all tests, including all linting tests:
+
+```bash
+npm test
+```
+
+All test files live beside the respective component. Testing setup, fixtures, and mocks can be found in `./jest.config.js` and `./jest` folder.
+
+For local development, run the test watcher:
+
+```bash
+npm run test:watch
+```
+
### 🎈 Add a new post
```bash
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 00000000..4c3eabea
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,19 @@
+module.exports = {
+ transform: {
+ '^.+\\.jsx?$': '/jest/jest-preprocess.js'
+ },
+ 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', '.cache', 'public', 'coverage'],
+ transformIgnorePatterns: ['node_modules/(?!(gatsby)/)'],
+ globals: {
+ __PATH_PREFIX__: ''
+ },
+ testURL: 'http://localhost',
+ setupFiles: ['/jest/loadershim.js'],
+ setupFilesAfterEnv: ['/jest/setup-test-env.js']
+}
diff --git a/jest/__mocks__/file-mock.js b/jest/__mocks__/file-mock.js
new file mode 100644
index 00000000..0e56c5b5
--- /dev/null
+++ b/jest/__mocks__/file-mock.js
@@ -0,0 +1 @@
+module.exports = 'test-file-stub'
diff --git a/jest/__mocks__/gatsby.js b/jest/__mocks__/gatsby.js
new file mode 100644
index 00000000..d325f6ea
--- /dev/null
+++ b/jest/__mocks__/gatsby.js
@@ -0,0 +1,28 @@
+const React = require('react')
+const gatsby = jest.requireActual('gatsby')
+
+module.exports = {
+ ...gatsby,
+ graphql: jest.fn(),
+ Link: jest.fn().mockImplementation(
+ // these props are invalid for an `a` tag
+ ({
+ /* eslint-disable no-unused-vars */
+ activeClassName,
+ activeStyle,
+ getProps,
+ innerRef,
+ ref,
+ replace,
+ to,
+ /* eslint-enable no-unused-vars */
+ ...rest
+ }) =>
+ React.createElement('a', {
+ ...rest,
+ href: to
+ })
+ ),
+ StaticQuery: jest.fn(),
+ useStaticQuery: jest.fn()
+}
diff --git a/jest/__mocks__/svgr-mock.js b/jest/__mocks__/svgr-mock.js
new file mode 100644
index 00000000..d067fa02
--- /dev/null
+++ b/jest/__mocks__/svgr-mock.js
@@ -0,0 +1 @@
+module.exports = { ReactComponent: 'svg' }
diff --git a/jest/jest-preprocess.js b/jest/jest-preprocess.js
new file mode 100644
index 00000000..95114e53
--- /dev/null
+++ b/jest/jest-preprocess.js
@@ -0,0 +1,5 @@
+const babelOptions = {
+ presets: ['babel-preset-gatsby']
+}
+
+module.exports = require('babel-jest').createTransformer(babelOptions)
diff --git a/jest/loadershim.js b/jest/loadershim.js
new file mode 100644
index 00000000..772dcc44
--- /dev/null
+++ b/jest/loadershim.js
@@ -0,0 +1,3 @@
+global.___loader = {
+ enqueue: jest.fn()
+}
diff --git a/jest/setup-test-env.js b/jest/setup-test-env.js
new file mode 100644
index 00000000..99c14cda
--- /dev/null
+++ b/jest/setup-test-env.js
@@ -0,0 +1,4 @@
+import 'jest-dom/extend-expect'
+
+// this is basically: afterEach(cleanup)
+import 'react-testing-library/cleanup-after-each'
diff --git a/jest/testRender.js b/jest/testRender.js
new file mode 100644
index 00000000..641cc90f
--- /dev/null
+++ b/jest/testRender.js
@@ -0,0 +1,11 @@
+import { render } from 'react-testing-library'
+
+const testRender = component => {
+ it('renders without crashing', () => {
+ const { container } = render(component)
+
+ expect(container.firstChild).toBeInTheDocument()
+ })
+}
+
+export default testRender
diff --git a/package.json b/package.json
index 1f9447e8..eba55022 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,8 @@
"lint:md": "markdownlint './**/*.{md,markdown}' --ignore './{node_modules,public,.cache,.git}/**/*'",
"lint:yaml": "prettier '**/*.{yml,yaml}' --list-different",
"lint": "run-p --continue-on-error lint:js lint:css lint:yaml lint:md",
- "test": "npm run lint",
+ "test": "npm run lint && jest --coverage",
+ "test:watch": "npm run lint && jest --coverage --watch",
"deploy": "./scripts/deploy.sh",
"new": "babel-node ./scripts/new.js"
},
@@ -33,36 +34,36 @@
"dms2dec": "^1.1.0",
"fast-exif": "^1.0.1",
"fraction.js": "^4.0.12",
- "gatsby": "^2.3.22",
- "gatsby-image": "^2.0.38",
+ "gatsby": "^2.4.2",
+ "gatsby-image": "^2.0.41",
"gatsby-plugin-catch-links": "^2.0.13",
- "gatsby-plugin-favicon": "^3.1.5",
- "gatsby-plugin-feed": "^2.1.0",
- "gatsby-plugin-lunr": "^1.4.0",
+ "gatsby-plugin-favicon": "^3.1.6",
+ "gatsby-plugin-feed": "^2.2.0",
+ "gatsby-plugin-lunr": "^1.5.0",
"gatsby-plugin-matomo": "^0.7.0",
"gatsby-plugin-meta-redirect": "^1.1.1",
- "gatsby-plugin-offline": "^2.0.25",
- "gatsby-plugin-react-helmet": "^3.0.11",
+ "gatsby-plugin-offline": "^2.1.0",
+ "gatsby-plugin-react-helmet": "^3.0.12",
"gatsby-plugin-sass": "^2.0.11",
- "gatsby-plugin-sharp": "^2.0.34",
- "gatsby-plugin-sitemap": "^2.0.12",
+ "gatsby-plugin-sharp": "^2.0.36",
+ "gatsby-plugin-sitemap": "^2.1.0",
"gatsby-plugin-svgr": "^2.0.2",
"gatsby-plugin-webpack-size": "^0.0.3",
"gatsby-redirect-from": "^0.1.1",
"gatsby-remark-autolink-headers": "^2.0.16",
- "gatsby-remark-copy-linked-files": "^2.0.11",
+ "gatsby-remark-copy-linked-files": "^2.0.12",
"gatsby-remark-highlights": "^1.3.4",
- "gatsby-remark-images": "^3.0.10",
+ "gatsby-remark-images": "^3.0.11",
"gatsby-remark-smartypants": "^2.0.9",
- "gatsby-source-filesystem": "^2.0.29",
+ "gatsby-source-filesystem": "^2.0.33",
"gatsby-source-graphql": "^2.0.18",
- "gatsby-transformer-remark": "^2.3.8",
- "gatsby-transformer-sharp": "^2.1.18",
- "graphql": "^14.2.0",
+ "gatsby-transformer-remark": "^2.3.12",
+ "gatsby-transformer-sharp": "^2.1.19",
+ "graphql": "^14.2.1",
"intersection-observer": "^0.6.0",
"js-scrypt": "^0.2.0",
"load-script": "^1.0.0",
- "node-sass": "^4.11.0",
+ "node-sass": "^4.12.0",
"nord": "^0.2.1",
"pigeon-maps": "^0.12.1",
"pigeon-marker": "^0.3.4",
@@ -70,7 +71,7 @@
"react-blockies": "^1.4.1",
"react-clipboard.js": "^2.0.7",
"react-dom": "^16.8.6",
- "react-helmet": "^5.2.0",
+ "react-helmet": "^5.2.1",
"react-modal": "^3.8.1",
"react-pose": "^4.0.8",
"react-qr-svg": "^2.2.1",
@@ -80,31 +81,36 @@
"remark-react": "^5.0.1",
"slugify": "^1.3.4",
"terser": "^3.17.0",
- "web3": "^1.0.0-beta.51"
+ "web3": "^1.0.0-beta.54"
},
"devDependencies": {
"@babel/node": "^7.2.2",
- "@babel/preset-env": "^7.4.2",
+ "@babel/preset-env": "^7.4.4",
"@svgr/webpack": "^4.2.0",
"babel-eslint": "^10.0.1",
+ "babel-jest": "^24.7.1",
"eslint": "^5.16.0",
- "eslint-config-prettier": "^4.1.0",
+ "eslint-config-prettier": "^4.2.0",
"eslint-loader": "^2.1.2",
"eslint-plugin-graphql": "^3.0.3",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-prettier": "^3.0.1",
- "eslint-plugin-react": "^7.12.4",
+ "eslint-plugin-react": "^7.13.0",
"fs-extra": "^7.0.1",
+ "identity-obj-proxy": "^3.0.0",
+ "jest": "^24.7.1",
+ "jest-dom": "^3.1.4",
"markdownlint-cli": "^0.15.0",
"npm-run-all": "^4.1.5",
- "ora": "^3.2.0",
+ "ora": "^3.4.0",
"pify": "^4.0.1",
"prettier": "^1.17.0",
"prettier-stylelint": "^0.4.2",
- "stylelint": "^10.0.0",
- "stylelint-config-css-modules": "^1.3.0",
+ "react-testing-library": "^7.0.0",
+ "stylelint": "^10.0.1",
+ "stylelint-config-css-modules": "^1.4.0",
"stylelint-config-standard": "^18.3.0",
- "stylelint-scss": "^3.5.4",
+ "stylelint-scss": "^3.6.1",
"why-did-you-update": "^1.0.6"
},
"engines": {
diff --git a/src/components/atoms/Container.test.jsx b/src/components/atoms/Container.test.jsx
new file mode 100644
index 00000000..370ce8af
--- /dev/null
+++ b/src/components/atoms/Container.test.jsx
@@ -0,0 +1,9 @@
+import React from 'react'
+// import { render } from 'react-testing-library'
+import testRender from '../../../jest/testRender'
+
+import Container from './Container'
+
+describe('Container', () => {
+ testRender(Hello)
+})
diff --git a/src/components/atoms/Exif.jsx b/src/components/atoms/Exif.jsx
index c805f3b3..ee788675 100644
--- a/src/components/atoms/Exif.jsx
+++ b/src/components/atoms/Exif.jsx
@@ -1,70 +1,8 @@
-import React, { Fragment, PureComponent } from 'react'
+import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import Map from 'pigeon-maps'
-import Marker from 'pigeon-marker'
+import ExifMap from './ExifMap'
import styles from './Exif.module.scss'
-const MAPBOX_ACCESS_TOKEN =
- 'pk.eyJ1Ijoia3JlbWFsaWNpb3VzIiwiYSI6ImNqbTE2NHpkYjJmNm8zcHF4eDVqZzk3ejEifQ.1uwPzM6MSTgL2e1Hxcmuqw'
-
-const retina =
- typeof window !== 'undefined' && window.devicePixelRatio >= 2 ? '@2x' : ''
-
-const mapbox = (mapboxId, accessToken) => (x, y, z) =>
- `https://api.mapbox.com/styles/v1/mapbox/${mapboxId}/tiles/256/${z}/${x}/${y}${retina}?access_token=${accessToken}`
-
-const providers = {
- osm: (x, y, z) => {
- const s = String.fromCharCode(97 + ((x + y + z) % 3))
- return `https://${s}.tile.openstreetmap.org/${z}/${x}/${y}.png`
- },
- wikimedia: (x, y, z) =>
- `https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}${retina}.png`,
- stamen: (x, y, z) =>
- `https://stamen-tiles.a.ssl.fastly.net/terrain/${z}/${x}/${y}${retina}.jpg`,
- streets: mapbox('streets-v10', MAPBOX_ACCESS_TOKEN),
- satellite: mapbox('satellite-streets-v10', MAPBOX_ACCESS_TOKEN),
- outdoors: mapbox('outdoors-v10', MAPBOX_ACCESS_TOKEN),
- light: mapbox('light-v9', MAPBOX_ACCESS_TOKEN),
- dark: mapbox('dark-v9', MAPBOX_ACCESS_TOKEN)
-}
-
-class ExifMap extends PureComponent {
- state = { zoom: 12 }
-
- static propTypes = {
- gps: PropTypes.object
- }
-
- zoomIn = () => {
- this.setState({
- zoom: Math.min(this.state.zoom + 4, 20)
- })
- }
-
- render() {
- const { latitude, longitude } = this.props.gps
-
- return (
-
- )
- }
-}
-
export default class Exif extends PureComponent {
static propTypes = {
exif: PropTypes.object
@@ -82,7 +20,7 @@ export default class Exif extends PureComponent {
} = this.props.exif
return (
-
+ <>
-
+ >
)
}
}
diff --git a/src/components/atoms/Exif.test.jsx b/src/components/atoms/Exif.test.jsx
new file mode 100644
index 00000000..b4c497af
--- /dev/null
+++ b/src/components/atoms/Exif.test.jsx
@@ -0,0 +1,19 @@
+import React from 'react'
+// import { render } from 'react-testing-library'
+import testRender from '../../../jest/testRender'
+
+import Exif from './Exif'
+
+const exif = {
+ iso: '500',
+ model: 'Canon',
+ fstop: '7.2',
+ shutterspeed: '200',
+ focalLength: '200',
+ exposure: '200',
+ gps: { latitude: '52.4792516', longitude: '13.431609' }
+}
+
+describe('Exif', () => {
+ testRender()
+})
diff --git a/src/components/atoms/ExifMap.jsx b/src/components/atoms/ExifMap.jsx
new file mode 100644
index 00000000..c2107739
--- /dev/null
+++ b/src/components/atoms/ExifMap.jsx
@@ -0,0 +1,65 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Map from 'pigeon-maps'
+import Marker from 'pigeon-marker'
+
+const MAPBOX_ACCESS_TOKEN =
+ 'pk.eyJ1Ijoia3JlbWFsaWNpb3VzIiwiYSI6ImNqbTE2NHpkYjJmNm8zcHF4eDVqZzk3ejEifQ.1uwPzM6MSTgL2e1Hxcmuqw'
+
+const retina =
+ typeof window !== 'undefined' && window.devicePixelRatio >= 2 ? '@2x' : ''
+
+const mapbox = (mapboxId, accessToken) => (x, y, z) =>
+ `https://api.mapbox.com/styles/v1/mapbox/${mapboxId}/tiles/256/${z}/${x}/${y}${retina}?access_token=${accessToken}`
+
+const providers = {
+ // osm: (x, y, z) => {
+ // const s = String.fromCharCode(97 + ((x + y + z) % 3))
+ // return `https://${s}.tile.openstreetmap.org/${z}/${x}/${y}.png`
+ // },
+ // wikimedia: (x, y, z) =>
+ // `https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}${retina}.png`,
+ // stamen: (x, y, z) =>
+ // `https://stamen-tiles.a.ssl.fastly.net/terrain/${z}/${x}/${y}${retina}.jpg`,
+ // streets: mapbox('streets-v10', MAPBOX_ACCESS_TOKEN),
+ // satellite: mapbox('satellite-streets-v10', MAPBOX_ACCESS_TOKEN),
+ // outdoors: mapbox('outdoors-v10', MAPBOX_ACCESS_TOKEN),
+ light: mapbox('light-v9', MAPBOX_ACCESS_TOKEN),
+ dark: mapbox('dark-v9', MAPBOX_ACCESS_TOKEN)
+}
+
+export default class ExifMap extends PureComponent {
+ state = { zoom: 12 }
+
+ static propTypes = {
+ gps: PropTypes.object
+ }
+
+ zoomIn = () => {
+ this.setState({
+ zoom: Math.min(this.state.zoom + 4, 20)
+ })
+ }
+
+ render() {
+ const { latitude, longitude } = this.props.gps
+
+ return (
+
+ )
+ }
+}
diff --git a/src/components/atoms/Hamburger.test.jsx b/src/components/atoms/Hamburger.test.jsx
new file mode 100644
index 00000000..9b6c61c9
--- /dev/null
+++ b/src/components/atoms/Hamburger.test.jsx
@@ -0,0 +1,9 @@
+import React from 'react'
+// import { render } from 'react-testing-library'
+import testRender from '../../../jest/testRender'
+
+import Hamburger from './Hamburger'
+
+describe('Hamburger', () => {
+ testRender()
+})
diff --git a/src/components/atoms/Input.test.jsx b/src/components/atoms/Input.test.jsx
new file mode 100644
index 00000000..52126d8e
--- /dev/null
+++ b/src/components/atoms/Input.test.jsx
@@ -0,0 +1,9 @@
+import React from 'react'
+// import { render } from 'react-testing-library'
+import testRender from '../../../jest/testRender'
+
+import Input from './Input'
+
+describe('Input', () => {
+ testRender()
+})