diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2d36678..150755d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -19,4 +19,7 @@ jobs:
key: v1-dependencies-{{ checksum "package.json" }}
- run:
name: Run Test
- command: yarn test
\ No newline at end of file
+ command: yarn test
+ - run:
+ name: Build the app
+ command: yarn build
\ No newline at end of file
diff --git a/README.md b/README.md
index e34eb16..4c7bb3b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
## React features 🤓
+[](https://circleci.com/gh/Will956/react-features/tree/master)
+
I usually use this repo in order to test new React features.
## Features
diff --git a/package.json b/package.json
index fb1c34d..32711db 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,9 @@
},
"eslintConfig": {
"extends": "react-app",
- "plugins": ["react-hooks"],
+ "plugins": [
+ "react-hooks"
+ ],
"rules": {
"react-hooks/rules-of-hooks": "error"
}
@@ -119,6 +121,7 @@
]
},
"devDependencies": {
- "eslint-plugin-react-hooks": "^1.0.1"
+ "eslint-plugin-react-hooks": "^1.0.1",
+ "react-testing-library": "^5.5.3"
}
}
diff --git a/src/components/Hooks/BasicHooks/index.js b/src/components/Hooks/BasicHooks/index.js
new file mode 100644
index 0000000..ba44c57
--- /dev/null
+++ b/src/components/Hooks/BasicHooks/index.js
@@ -0,0 +1,15 @@
+import React, { useState } from 'react';
+
+const BasicHooks = () => {
+ const [count, setCount] = useState(0);
+
+ return (
+ <>
+
+
+
Counter: {count}
+ >
+ )
+};
+
+export default BasicHooks;
diff --git a/src/components/Hooks/BasicHooks/index.test.js b/src/components/Hooks/BasicHooks/index.test.js
new file mode 100644
index 0000000..ba32a3f
--- /dev/null
+++ b/src/components/Hooks/BasicHooks/index.test.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import { cleanup, fireEvent, render } from 'react-testing-library';
+import BasicHooks from './index';
+
+afterEach(cleanup);
+
+describe('BasicHooks', () => {
+ let counter;
+ let addBtn;
+ let removeBtn;
+
+ beforeEach(() => {
+ const { container, getByText } = render();
+ counter = container.querySelector('p');
+ addBtn = getByText('Add one more');
+ removeBtn = getByText('Remove one more');
+ });
+
+ it('renders BasicHooks', () => {
+ expect(counter.textContent).toBe('Counter: 0');
+ });
+
+ it('update BasicHooks counter', () => {
+ fireEvent.click(addBtn);
+ expect(counter.textContent).toBe('Counter: 1');
+ });
+
+ it('update BasicHooks counter', () => {
+ fireEvent.click(removeBtn);
+ expect(counter.textContent).toBe('Counter: -1');
+ });
+});
\ No newline at end of file
diff --git a/src/components/Hooks/ContextHooks/index.js b/src/components/Hooks/ContextHooks/index.js
new file mode 100644
index 0000000..ce362ef
--- /dev/null
+++ b/src/components/Hooks/ContextHooks/index.js
@@ -0,0 +1,11 @@
+import React, { useContext } from 'react';
+
+import { ThemeContext } from '../../../containers/Playground';
+
+const ContextHooks = () => {
+ const theme = useContext(ThemeContext);
+
+ return <>Theme is: {theme}
>;
+};
+
+export default ContextHooks;
diff --git a/src/components/Hooks/ContextHooks/index.test.js b/src/components/Hooks/ContextHooks/index.test.js
new file mode 100644
index 0000000..34e8664
--- /dev/null
+++ b/src/components/Hooks/ContextHooks/index.test.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import { cleanup, render } from 'react-testing-library';
+import ContextHooks from './index';
+import { ThemeContext } from '../../../containers/Playground';
+
+afterEach(cleanup);
+
+describe('ContextHooks', () => {
+ it('renders EffectHooks and should have blue as theme', () => {
+ const { container } = render();
+ expect(container.querySelector('p').textContent).toBe('Theme is: random color');
+ });
+});
\ No newline at end of file
diff --git a/src/components/Hooks/EffectHooks/index.js b/src/components/Hooks/EffectHooks/index.js
new file mode 100644
index 0000000..eb63deb
--- /dev/null
+++ b/src/components/Hooks/EffectHooks/index.js
@@ -0,0 +1,22 @@
+import React, { useState, useEffect } from 'react';
+
+const EffectHooks = () => {
+ const [count, setCount] = useState(0);
+ const [status, setStatus] = useState(null);
+
+ useEffect(() => {
+ setStatus(`Component mounted/updated with ${count}`);
+ return () => console.log('[EffectHooks] Component was unmounted');
+ }, [count]); // only re-run if count changes 🤓
+
+ return (
+ <>
+
+
+ Counter: {count}
+ {status && {status}
}
+ >
+ );
+};
+
+export default EffectHooks;
diff --git a/src/components/Hooks/EffectHooks/index.test.js b/src/components/Hooks/EffectHooks/index.test.js
new file mode 100644
index 0000000..04b6fb9
--- /dev/null
+++ b/src/components/Hooks/EffectHooks/index.test.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import { cleanup, fireEvent, render } from 'react-testing-library';
+import EffectHooks from './index';
+
+global.console = {
+ log: jest.fn()
+};
+
+afterEach(cleanup);
+
+describe('BasicHooks', () => {
+ it('renders EffectHooks', () => {
+ const { getByTestId } = render();
+ expect(getByTestId('counter').textContent).toBe('Counter: 0');
+ expect(getByTestId('status').textContent).toBe('Component mounted/updated with 0');
+ });
+
+ it('update status when component did update', () => {
+ const { getByTestId, getByText } = render();
+ const addBtn = getByText('Add one more');
+ fireEvent.click(addBtn);
+ expect(getByTestId('status').textContent).toBe('Component mounted/updated with 1');
+ });
+
+ it('trigger console.log when component did unmount', () => {
+ const { getByText } = render();
+ const addBtn = getByText('Add one more');
+ fireEvent.click(addBtn);
+ expect(console.log).toHaveBeenCalledWith('[EffectHooks] Component was unmounted');
+ });
+});
\ No newline at end of file
diff --git a/src/components/Hooks/ReducerHooks/index.js b/src/components/Hooks/ReducerHooks/index.js
new file mode 100644
index 0000000..e40dcc4
--- /dev/null
+++ b/src/components/Hooks/ReducerHooks/index.js
@@ -0,0 +1,39 @@
+import React, { useReducer } from 'react';
+
+const initialState = {
+ count: 0,
+ previous: 0
+};
+
+const reducer = (state, action) => {
+ switch (action.type) {
+ case 'INCREMENT':
+ return {
+ count: state.count + 1,
+ previous: state.count
+ };
+ case 'DECREMENT':
+ return {
+ count: state.count -1,
+ previous: state.count,
+ }
+
+ default:
+ return state;
+ }
+};
+
+const ReducerHooks = () => {
+ const [{ count, previous }, dispatch] = useReducer(reducer, initialState);
+
+ return (
+ <>
+
+
+ Count is: {count}
+ Previous was: {previous}
+ >
+ );
+};
+
+export default ReducerHooks;
diff --git a/src/components/Hooks/ReducerHooks/index.test.js b/src/components/Hooks/ReducerHooks/index.test.js
new file mode 100644
index 0000000..ef35624
--- /dev/null
+++ b/src/components/Hooks/ReducerHooks/index.test.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import { cleanup, fireEvent, render } from 'react-testing-library';
+import ReducerHooks from './index';
+
+//TODO: Add more tests (ie. test is fn called..)
+
+afterEach(cleanup);
+
+describe('ReducerHooks', () => {
+ it('renders ReducerHooks', () => {
+ const { container } = render();
+ expect(container.querySelectorAll('p')[0].textContent).toBe('Count is: 0');
+ expect(container.querySelectorAll('p')[1].textContent).toBe('Previous was: 0');
+ });
+
+ it('update ReducerHooks counter', () => {
+ const { container, getByText } = render();
+
+ const addBtn = getByText('Add one more');
+ fireEvent.click(addBtn);
+
+ expect(container.querySelectorAll('p')[0].textContent).toBe('Count is: 1');
+ expect(container.querySelectorAll('p')[1].textContent).toBe('Previous was: 0');
+ });
+});
\ No newline at end of file
diff --git a/src/containers/Playground/index.js b/src/containers/Playground/index.js
index f804090..0287fe2 100644
--- a/src/containers/Playground/index.js
+++ b/src/containers/Playground/index.js
@@ -1,14 +1,35 @@
-import React, { Component } from 'react';
+import React, { createContext } from 'react';
+
+import BasicHooks from '../../components/Hooks/BasicHooks';
+import EffectHooks from '../../components/Hooks/EffectHooks';
+import ContextHooks from '../../components/Hooks/ContextHooks';
+import ReducerHooks from '../../components/Hooks/ReducerHooks';
+
import './index.scss';
-class Playground extends Component {
- render() {
- return (
-
- );
- }
-}
+export const ThemeContext = createContext();
+
+const Playground = () => (
+
+
+
+);
export default Playground;
diff --git a/src/containers/Playground/index.scss b/src/containers/Playground/index.scss
index 924df6c..725f624 100644
--- a/src/containers/Playground/index.scss
+++ b/src/containers/Playground/index.scss
@@ -1,4 +1,4 @@
-.App-header {
+.app-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
@@ -8,3 +8,10 @@
font-size: calc(10px + 2vmin);
color: white;
}
+
+.app-block {
+ padding: 30px 30px;
+ margin: 10px 0;
+ text-align: center;
+ border: 1px solid white;
+}
diff --git a/yarn.lock b/yarn.lock
index 35e9ac9..ae28b53 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -834,6 +834,13 @@
dependencies:
regenerator-runtime "^0.12.0"
+"@babel/runtime@^7.1.5", "@babel/runtime@^7.3.1":
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a"
+ integrity sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==
+ dependencies:
+ regenerator-runtime "^0.12.0"
+
"@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2":
version "7.2.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
@@ -885,6 +892,11 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
+"@sheerun/mutationobserver-shim@^0.3.2":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b"
+ integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==
+
"@svgr/core@^2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@svgr/core/-/core-2.4.1.tgz#03a407c28c4a1d84305ae95021e8eabfda8fa731"
@@ -3121,6 +3133,16 @@ dom-serializer@0:
domelementtype "~1.1.1"
entities "~1.1.1"
+dom-testing-library@^3.13.1:
+ version "3.16.5"
+ resolved "https://registry.yarnpkg.com/dom-testing-library/-/dom-testing-library-3.16.5.tgz#8c71f127c6b4ee48115660798040291b59dfc894"
+ integrity sha512-t3OaTcDdsAqtAZNeZ13KnOJmt+2HaDJqYWyf0iBRzbG6GwrNtpF0122Ygu/qkerIwcnHMX1ihwZVx/DhaLpmTw==
+ dependencies:
+ "@babel/runtime" "^7.1.5"
+ "@sheerun/mutationobserver-shim" "^0.3.2"
+ pretty-format "^24.0.0"
+ wait-for-expect "^1.1.0"
+
domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -7881,6 +7903,14 @@ pretty-format@^23.6.0:
ansi-regex "^3.0.0"
ansi-styles "^3.2.0"
+pretty-format@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.0.0.tgz#cb6599fd73ac088e37ed682f61291e4678f48591"
+ integrity sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==
+ dependencies:
+ ansi-regex "^4.0.0"
+ ansi-styles "^3.2.0"
+
private@^0.1.6, private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -8141,6 +8171,14 @@ react-error-overlay@^5.1.0:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.2.tgz#888957b884d4b25b083a82ad550f7aad96585394"
integrity sha512-7kEBKwU9R8fKnZJBRa5RSIfay4KJwnYvKB6gODGicUmDSAhQJ7Tdnll5S0RLtYrzRfMVXlqYw61rzrSpP4ThLQ==
+react-testing-library@^5.5.3:
+ version "5.5.3"
+ resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-5.5.3.tgz#cfd32da95b29e475003df4f66bd96e34eb5624bd"
+ integrity sha512-67jMsSJHbbm9M0NWvEzjNikDAKRdxivhP6vnpa9xPg/fYh19zkE4rMsFh5YWLpyoomm+e49fg+ubcXaEBYartA==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ dom-testing-library "^3.13.1"
+
react@^16.8.0:
version "16.8.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.1.tgz#ae11831f6cb2a05d58603a976afc8a558e852c4a"
@@ -9837,6 +9875,11 @@ w3c-xmlserializer@^1.0.1:
webidl-conversions "^4.0.2"
xml-name-validator "^3.0.0"
+wait-for-expect@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.1.0.tgz#6607375c3f79d32add35cd2c87ce13f351a3d453"
+ integrity sha512-vQDokqxyMyknfX3luCDn16bSaRcOyH6gGuUXMIbxBLeTo6nWuEWYqMTT9a+44FmW8c2m6TRWBdNvBBjA1hwEKg==
+
walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"