diff --git a/client/__mocks__/user-mock.ts b/client/__mocks__/user-mock.ts new file mode 100644 index 0000000..1f4e32e --- /dev/null +++ b/client/__mocks__/user-mock.ts @@ -0,0 +1,31 @@ +const userMock = { + isLogged: false, + isLoading: false, + isWeb3: false, + isNile: false, + account: '', + web3: {}, + ocean: {}, + balance: { eth: 0, ocn: 0 }, + network: '', + requestFromFaucet: jest.fn(), + unlockAccounts: jest.fn(), + message: '' +} + +const userMockConnected = { + isLogged: true, + isLoading: false, + isWeb3: true, + isNile: true, + account: '0xxxxxx', + web3: {}, + ocean: {}, + balance: { eth: 0, ocn: 0 }, + network: '', + requestFromFaucet: jest.fn(), + unlockAccounts: jest.fn(), + message: '' +} + +export { userMock, userMockConnected } diff --git a/client/package-lock.json b/client/package-lock.json index a929be0..fc68d7b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1242,14 +1242,14 @@ "integrity": "sha512-p2n505t2K0zD1ZvGPhI6EsSviEVLCB7BYowhf/ONmVaWED138PaG4Z9nY6YuHU383uOoIWT+Lq3dLkFzDzstXw==" }, "@oceanprotocol/keeper-contracts": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@oceanprotocol/keeper-contracts/-/keeper-contracts-0.9.1.tgz", - "integrity": "sha512-c1LvaH+e1tzow0gZLwSWe19ap+DrZuNmZfxBdwEVEPQXarI0jTXa5qVDoiBow8kBWaqSIUgFAzQOJW8rKdlS1A==" + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/@oceanprotocol/keeper-contracts/-/keeper-contracts-0.9.7.tgz", + "integrity": "sha512-nOpbSE/BG+tQBfLXZ/EqSOvUPzOuot84vHxjAfEU8K3v4eOnqFJVo+oyB7KlcF87wBJXDmi/Ir9qHY4c0Saipg==" }, "@oceanprotocol/squid": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@oceanprotocol/squid/-/squid-0.5.7.tgz", - "integrity": "sha512-q2rdlVT4n0IW8FcTrE9sfyl8lC0moAKJillUSfWadZHjdaVikBTS3iiPiKn8RIvfBL4j3RxlVHdGQdEl8rIDfA==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@oceanprotocol/squid/-/squid-0.5.8.tgz", + "integrity": "sha512-1hEOA5rF8qsLF6uSMVBAN4c6Licn2FCGdWLCHpZ6ePzyA2/3RXxhreUE/0mcTkmdITwlx1rCFFO+rYhDqgyPVQ==", "requires": { "@oceanprotocol/keeper-contracts": "^0.9.1", "bignumber.js": "^8.1.1", @@ -1606,6 +1606,16 @@ "resolved": "https://registry.npmjs.org/@oceanprotocol/typographies/-/typographies-0.1.0.tgz", "integrity": "sha512-kMsZsqvzpz9KzVbVZzllwhPoIC3zbqsdRrClagZL/C2PHzgLrKGC1kYn3gPt0RMIFg9ZjrwieKaxlgIK9i9zzg==" }, + "@react-mock/state": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@react-mock/state/-/state-0.1.8.tgz", + "integrity": "sha512-oX16w3FKhfF2+nQE+C4frrfVddzwhF4YEYxF8frXzboEDZsxCRpFuQC0za3T59ZxI4ygBarBqDrhYzipGsD9TQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.1.2", + "lodash": "^4.17.11" + } + }, "@sheerun/mutationobserver-shim": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", @@ -1900,9 +1910,9 @@ "dev": true }, "@types/jest": { - "version": "24.0.11", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.11.tgz", - "integrity": "sha512-2kLuPC5FDnWIDvaJBzsGTBQaBbnDweznicvK7UGYzlIJP4RJR2a4A/ByLUXEyEgag6jz8eHdlWExGDtH3EYUXQ==", + "version": "24.0.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.12.tgz", + "integrity": "sha512-60sjqMhat7i7XntZckcSGV8iREJyXXI6yFHZkSZvCPUeOnEJ/VP1rU/WpEWQ56mvoh8NhC+sfKAuJRTyGtCOow==", "dev": true, "requires": { "@types/jest-diff": "*" @@ -1932,9 +1942,9 @@ "dev": true }, "@types/react": { - "version": "16.8.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.14.tgz", - "integrity": "sha512-26tFVJ1omGmzIdFTFmnC5zhz1GTaqCjxgUxV4KzWvsybF42P7/j4RBn6UeO3KbHPXqKWZszMXMoI65xIWm954A==", + "version": "16.8.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.15.tgz", + "integrity": "sha512-dMhzw1rWK+wwJWvPp5Pk12ksSrm/z/C/+lOQbMZ7YfDQYnJ02bc0wtg4EJD9qrFhuxFrf/ywNgwTboucobJqQg==", "dev": true, "requires": { "@types/prop-types": "*", @@ -2009,9 +2019,9 @@ } }, "@types/react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-hP7vUaZMVSWKxo133P8U51U6UZ7+pbY+eAQb8+p6SZ2rB1rj3mOTDgTzhhi+R2SCB4S+sWekAAGoxdiZPG0ReQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.1.tgz", + "integrity": "sha512-1usq4DRUVBFnxc9KGJAlJO9EpQrLZGDDEC8wDOn2+2ODSyudYo8FiIzPDRaX/hfQjHqGeeoNaNdA2bj0l35hZQ==", "dev": true, "requires": { "@types/react": "*" @@ -5145,7 +5155,8 @@ "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true }, "deep-is": { "version": "0.1.3", @@ -5439,15 +5450,15 @@ } }, "dom-testing-library": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/dom-testing-library/-/dom-testing-library-3.19.3.tgz", - "integrity": "sha512-oiI+oq91iO/Vpp+pt8PqfqLfBK074FH0eprhoFNvBCvJOk7vL4ozbe/yj/kEEGR6kiT4F3MAam19AX1fdGFjrA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/dom-testing-library/-/dom-testing-library-4.0.1.tgz", + "integrity": "sha512-Yr0yWlpI2QdTDEgPEk0TEekwP4VyZlJpl9E7nKP2FCKni44cb1jzjsy9KX6hBDsNA7EVlPpq9DHzO2eoEaqDZg==", "dev": true, "requires": { - "@babel/runtime": "^7.3.4", + "@babel/runtime": "^7.4.3", "@sheerun/mutationobserver-shim": "^0.3.2", - "pretty-format": "^24.5.0", - "wait-for-expect": "^1.1.0" + "pretty-format": "^24.7.0", + "wait-for-expect": "^1.1.1" } }, "dom-walk": { @@ -8810,9 +8821,9 @@ } }, "jest-dom": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/jest-dom/-/jest-dom-3.1.3.tgz", - "integrity": "sha512-V9LdySiA74/spcAKEG3FRMRKnisKlcYr3EeCNYI4n7CWNE7uYg5WoBUHeGXirjWjRYLLZ5vx8rUaR/6x6o75oQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jest-dom/-/jest-dom-3.1.4.tgz", + "integrity": "sha512-ruIRHoRVnqPRt/HSS2aFukfhTpjEoq1I6PkYptKK5U2EeRm1eeOXG7BFiaMncTaGu4COSoCF84oLHj02+J5VDg==", "dev": true, "requires": { "chalk": "^2.4.1", @@ -10984,9 +10995,9 @@ } }, "node-fetch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", - "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.5.0.tgz", + "integrity": "sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw==" }, "node-forge": { "version": "0.7.5", @@ -13049,9 +13060,9 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.4.2.tgz", - "integrity": "sha512-DfJqAen17LfLA3rQ+H5S4uXphrF+ANU1lT2ijds4V/Tj4gZxA3gx5/tg1bz7kYCmwna7LyJNCYqO7jNRzo3aLw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.5.0.tgz", + "integrity": "sha512-TYC4hDjZSvVxLMEucDMySkuAS9UIzSbAiYGyA9GWCjLKB8fQpviFbjd20fD7uejCDxZS+ftSdBKE6DS+xucJFg==", "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", @@ -13178,9 +13189,9 @@ } }, "react-datepicker": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.4.0.tgz", - "integrity": "sha512-ZOY7oYmZt+jeSFGj4NHNdCg7WzzIljPui98lGRd7YHNPO3B8Re4WVNALktp/x+mz1ofNO+TPzodMLMXzqjAUnw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.5.0.tgz", + "integrity": "sha512-X8D+5CqumSUwoq/x5d8O0neYDRmPmwVcyCktVbs5juXV4s73beV6sMf87hM6lxMSanWeB+jpx8waxJcPqsmCIg==", "requires": { "classnames": "^2.2.5", "date-fns": "^2.0.0-alpha.23", @@ -13359,19 +13370,24 @@ "integrity": "sha512-O9JRum1Zq/qCPFH5qVEvDDrVun8Jv9vbHtZXCR1EuRj9sKg1xJTlHxBzU6AkCzpvxRLuiY4OKImy3cDLQ+UTdg==", "dev": true }, + "react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + }, "react-ga": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/react-ga/-/react-ga-2.5.7.tgz", "integrity": "sha512-UmATFaZpEQDO96KFjB5FRLcT6hFcwaxOmAJZnjrSiFN/msTqylq9G+z5Z8TYzN/dbamDTiWf92m6MnXXJkAivQ==" }, "react-helmet": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-5.2.0.tgz", - "integrity": "sha1-qBgR3yExOm1VxfBYxK66XW89l6c=", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-5.2.1.tgz", + "integrity": "sha512-CnwD822LU8NDBnjCpZ4ySh8L6HYyngViTZLfBBb3NjtrpN8m49clH8hidHouq20I51Y6TpCTISCBbqiY5GamwA==", "requires": { - "deep-equal": "^1.0.1", "object-assign": "^4.1.1", "prop-types": "^15.5.4", + "react-fast-compare": "^2.0.2", "react-side-effect": "^1.1.0" } }, @@ -13568,13 +13584,13 @@ } }, "react-testing-library": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/react-testing-library/-/react-testing-library-6.1.2.tgz", - "integrity": "sha512-z69lhRDGe7u/NOjDCeFRoe1cB5ckJ4656n0tj/Fdcr6OoBUu7q9DBw0ftR7v5i3GRpdSWelnvl+feZFOyXyxwg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/react-testing-library/-/react-testing-library-7.0.0.tgz", + "integrity": "sha512-8SHqwG+uhN9VhAgNVkVa3f7VjTw/L5CIaoAxKmy+EZuDQ6O+VsfcpRAyUw3MDL1h8S/gGrEiazmHBVL/uXsftA==", "dev": true, "requires": { - "@babel/runtime": "^7.4.2", - "dom-testing-library": "^3.19.0" + "@babel/runtime": "^7.4.3", + "dom-testing-library": "^4.0.0" } }, "react-transition-group": { @@ -15826,9 +15842,9 @@ } }, "typescript": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.4.tgz", - "integrity": "sha512-xt5RsIRCEaf6+j9AyOBgvVuAec0i92rgCaS3S+UVf5Z/vF2Hvtsw08wtUTJqp4djwznoAgjSxeCcU4r+CcDBJA==", + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", + "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", "dev": true }, "ua-parser-js": { diff --git a/client/package.json b/client/package.json index c5f0def..6984755 100644 --- a/client/package.json +++ b/client/package.json @@ -6,14 +6,14 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts --max_old_space_size=4096 build", - "test": "react-scripts test --coverage", - "test:watch": "react-scripts test --coverage --watch", + "test": "react-scripts test --coverage --watchAll=false", + "test:watch": "react-scripts test --coverage", "eject": "react-scripts eject", "coverage": "cat coverage/lcov.info | codacy-coverage --token 8801f827fe1144ffa85cd7da94f2bbf7" }, "dependencies": { "@oceanprotocol/art": "^2.2.0", - "@oceanprotocol/squid": "^0.5.7", + "@oceanprotocol/squid": "^0.5.8", "@oceanprotocol/typographies": "^0.1.0", "classnames": "^2.2.6", "ethereum-blockies": "MyEtherWallet/blockies", @@ -21,14 +21,14 @@ "history": "^4.9.0", "is-url": "^1.2.4", "moment": "^2.24.0", - "query-string": "^6.4.2", + "query-string": "^6.5.0", "react": "^16.8.6", - "react-datepicker": "^2.3.0", + "react-datepicker": "^2.5.0", "react-dom": "^16.8.6", - "react-dotdotdot": "^1.2.3", + "react-dotdotdot": "^1.3.0", "react-ga": "^2.5.7", - "react-helmet": "^5.2.0", - "react-markdown": "^4.0.6", + "react-helmet": "^5.2.1", + "react-markdown": "^4.0.8", "react-moment": "^0.9.2", "react-paginate": "^6.3.0", "react-popper": "^1.3.3", @@ -38,24 +38,25 @@ "web3": "1.0.0-beta.37" }, "devDependencies": { + "@react-mock/state": "^0.1.8", "@types/classnames": "^2.2.7", "@types/filesize": "^4.1.0", "@types/is-url": "^1.2.28", - "@types/jest": "^24.0.11", - "@types/react": "^16.8.13", - "@types/react-datepicker": "^2.2.1", - "@types/react-dom": "^16.8.3", + "@types/jest": "^24.0.12", + "@types/react": "^16.8.15", + "@types/react-datepicker": "^2.3.0", + "@types/react-dom": "^16.8.4", "@types/react-dotdotdot": "^1.2.0", "@types/react-helmet": "^5.0.8", "@types/react-paginate": "^6.2.1", - "@types/react-router-dom": "^4.3.1", - "@types/react-transition-group": "^2.8.0", + "@types/react-router-dom": "^4.3.2", + "@types/react-transition-group": "^2.9.1", "@types/web3": "^1.0.18", - "jest-dom": "^3.1.3", + "jest-dom": "^3.1.4", "node-sass": "^4.12.0", "react-scripts": "^3.0.0", - "react-testing-library": "^6.1.2", - "typescript": "^3.4.3" + "react-testing-library": "^7.0.0", + "typescript": "^3.4.5" }, "repository": { "type": "git", @@ -66,5 +67,11 @@ "not dead", "not ie <= 11", "not op_mini all" - ] + ], + "jest": { + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/serviceWorker.ts" + ] + } } diff --git a/client/src/App.test.tsx b/client/src/App.test.tsx index fa6de58..8c85a79 100644 --- a/client/src/App.test.tsx +++ b/client/src/App.test.tsx @@ -1,10 +1,25 @@ import React from 'react' import { render } from 'react-testing-library' import App from './App' +import { User } from './context' +import { userMock } from '../__mocks__/user-mock' describe('App', () => { + it('should be able to run tests', () => { + expect(1 + 2).toEqual(3) + }) + it('renders without crashing', () => { const { container } = render() expect(container.firstChild).toBeInTheDocument() }) + + it('renders loading state', () => { + const { container } = render( + + + + ) + expect(container.querySelector('.spinner')).toBeInTheDocument() + }) }) diff --git a/client/src/App.tsx b/client/src/App.tsx index bddc57f..6fb1692 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -4,7 +4,6 @@ import Header from './components/organisms/Header' import Footer from './components/organisms/Footer' import Spinner from './components/atoms/Spinner' import { User } from './context' -import UserProvider from './context/UserProvider' import Routes from './Routes' import './styles/global.scss' import styles from './App.module.scss' @@ -12,33 +11,27 @@ import styles from './App.module.scss' export default class App extends Component { public render() { return ( - -
- - <> -
+
+ + <> +
-
- - {states => - states.isLoading ? ( -
- -
- ) : ( - - ) - } -
-
+
+ {this.context.isLoading ? ( +
+ +
+ ) : ( + + )} +
-
- - -
- +
+ + +
) } } + +App.contextType = User diff --git a/client/src/Routes.test.tsx b/client/src/Routes.test.tsx new file mode 100644 index 0000000..5ae671b --- /dev/null +++ b/client/src/Routes.test.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import { BrowserRouter as Router } from 'react-router-dom' +import { render } from 'react-testing-library' +import Routes from './Routes' + +describe('Routes', () => { + it('renders without crashing', () => { + const { container } = render( + + + + ) + expect(container.firstChild).toBeInTheDocument() + }) +}) diff --git a/client/src/components/atoms/Account.test.tsx b/client/src/components/atoms/Account.test.tsx new file mode 100644 index 0000000..da39bd7 --- /dev/null +++ b/client/src/components/atoms/Account.test.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import { render } from 'react-testing-library' +import { toDataUrl } from 'ethereum-blockies' +import Account from './Account' + +describe('Account', () => { + it('renders without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) + + it('outputs empty state without account', () => { + const { container } = render() + expect(container.firstChild).toHaveTextContent('No account selected') + }) + + it('outputs blockie img', () => { + const account = '0xxxxxxxxxxxxxxx' + const blockies = toDataUrl(account) + + const { container } = render() + expect(container.querySelector('.blockies')).toBeInTheDocument() + expect(container.querySelector('.blockies')).toHaveAttribute( + 'src', + blockies + ) + }) +}) diff --git a/client/src/components/atoms/Account.tsx b/client/src/components/atoms/Account.tsx index 0ff8cd3..de656e2 100644 --- a/client/src/components/atoms/Account.tsx +++ b/client/src/components/atoms/Account.tsx @@ -4,7 +4,7 @@ import { toDataUrl } from 'ethereum-blockies' import styles from './Account.module.scss' const Account = ({ account }: { account: string }) => { - const blockies = toDataUrl(account) + const blockies = account && toDataUrl(account) return account && blockies ? (
diff --git a/client/src/components/atoms/CategoryImage.test.tsx b/client/src/components/atoms/CategoryImage.test.tsx index 8e9fca4..1b07d03 100644 --- a/client/src/components/atoms/CategoryImage.test.tsx +++ b/client/src/components/atoms/CategoryImage.test.tsx @@ -1,16 +1,13 @@ import React from 'react' import { render } from 'react-testing-library' -import slugify from 'slugify' import CategoryImage from './CategoryImage' import formPublish from '../../data/form-publish.json' describe('CategoryImage', () => { it('renders fallback image', () => { - const { container, getByTestId } = render( - - ) + const { container } = render() expect(container.firstChild).toBeInTheDocument() - expect(getByTestId('image').style.backgroundImage).toMatch( + expect(container.firstChild.style.backgroundImage).toMatch( /jellyfish-back/ ) }) @@ -21,13 +18,8 @@ describe('CategoryImage', () => { : [] options.map((category: string) => { - const { getByTestId } = render( - - ) - expect(getByTestId('image')).toBeInTheDocument() - // expect(getByTestId('image').style.backgroundImage).toMatch( - // slugify(category, { lower: true }) - // ) + const { container } = render() + expect(container.firstChild).toBeInTheDocument() }) }) }) diff --git a/client/src/components/atoms/CategoryImage.tsx b/client/src/components/atoms/CategoryImage.tsx index 52c77c4..a02df06 100644 --- a/client/src/components/atoms/CategoryImage.tsx +++ b/client/src/components/atoms/CategoryImage.tsx @@ -148,7 +148,6 @@ export default class CategoryImage extends PureComponent<{ category: string }> { style={{ backgroundImage: `url(${image})` }} - {...this.props} /> ) } diff --git a/client/src/components/atoms/Form/Form.test.tsx b/client/src/components/atoms/Form/Form.test.tsx new file mode 100644 index 0000000..99a9d4c --- /dev/null +++ b/client/src/components/atoms/Form/Form.test.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { render } from 'react-testing-library' +import Form from './Form' + +describe('Form', () => { + it('renders without crashing', () => { + const { container } = render(
Hello
) + expect(container.firstChild).toBeInTheDocument() + }) + + it('renders title & description when set', () => { + const { container } = render( +
+ Hello +
+ ) + expect(container.querySelector('.formTitle')).toHaveTextContent( + 'Hello Title' + ) + expect(container.querySelector('.formDescription')).toHaveTextContent( + 'Hello Description' + ) + }) + + it('can switch to minimal', () => { + const { container } = render(
Hello
) + expect(container.firstChild).toHaveClass('formMinimal') + }) +}) diff --git a/client/src/components/atoms/Form/Input.test.tsx b/client/src/components/atoms/Form/Input.test.tsx new file mode 100644 index 0000000..9fa42ed --- /dev/null +++ b/client/src/components/atoms/Form/Input.test.tsx @@ -0,0 +1,90 @@ +import React from 'react' +import { render } from 'react-testing-library' +import Input from './Input' + +describe('Input', () => { + it('renders default without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + expect(container.querySelector('.label')).toHaveTextContent('My Input') + expect(container.querySelector('.input')).toHaveAttribute( + 'id', + 'my-input' + ) + }) + + it('renders as text input by default', () => { + const { container } = render() + expect(container.querySelector('.input')).toHaveAttribute( + 'type', + 'text' + ) + }) + + it('renders search', () => { + const { container } = render( + + ) + expect(container.querySelector('.input')).toHaveAttribute( + 'type', + 'search' + ) + expect(container.querySelector('label + div')).toHaveClass( + 'inputWrapSearch' + ) + }) + + it('renders select', () => { + const { container } = render( + + ) + expect(container.querySelector('select')).toBeInTheDocument() + }) + + it('renders textarea', () => { + const { container } = render( + + ) + expect(container.querySelector('textarea')).toBeInTheDocument() + }) + + it('renders radios', () => { + const { container } = render( + + ) + expect(container.querySelector('input[type=radio]')).toBeInTheDocument() + }) + + it('renders checkboxes', () => { + const { container } = render( + + ) + expect( + container.querySelector('input[type=checkbox]') + ).toBeInTheDocument() + }) + + it('renders date picker', () => { + const { container } = render( + + ) + expect( + container.querySelector('.react-datepicker-wrapper') + ).toBeInTheDocument() + }) +}) diff --git a/client/src/components/atoms/Form/Input.tsx b/client/src/components/atoms/Form/Input.tsx index 3f732e5..d6e5433 100644 --- a/client/src/components/atoms/Form/Input.tsx +++ b/client/src/components/atoms/Form/Input.tsx @@ -60,9 +60,8 @@ export default class Input extends PureComponent { } private handleDateChange = (date: Date) => { - this.setState({ - dateCreated: date - }) + this.setState({ dateCreated: date }) + const event = { currentTarget: { name: 'dateCreated', @@ -80,7 +79,8 @@ export default class Input extends PureComponent { name, required, onChange, - value + value, + rows } = this.props const wrapClass = this.inputWrapClasses() @@ -119,7 +119,7 @@ export default class Input extends PureComponent { className={styles.input} onFocus={this.toggleFocus} onBlur={this.toggleFocus} - {...this.props} + rows={rows} />
) @@ -174,6 +174,7 @@ export default class Input extends PureComponent { { ) : ( { + it('renders without crashing', () => { + const { container } = render(Hello) + expect(container.firstChild).toBeInTheDocument() + }) +}) diff --git a/client/src/components/atoms/Form/Label.test.tsx b/client/src/components/atoms/Form/Label.test.tsx new file mode 100644 index 0000000..a0a6604 --- /dev/null +++ b/client/src/components/atoms/Form/Label.test.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import { render } from 'react-testing-library' +import Label from './Label' + +describe('Label', () => { + it('renders without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) + + it('renders required state', () => { + const { container } = render( + + ) + expect(container.firstChild).toHaveAttribute('title', 'Required') + expect(container.firstChild).toHaveClass('required') + }) +}) diff --git a/client/src/components/atoms/Form/Row.test.tsx b/client/src/components/atoms/Form/Row.test.tsx new file mode 100644 index 0000000..3416d91 --- /dev/null +++ b/client/src/components/atoms/Form/Row.test.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import { render } from 'react-testing-library' +import Row from './Row' + +describe('Row', () => { + it('renders without crashing', () => { + const { container } = render(Hello) + expect(container.firstChild).toBeInTheDocument() + }) +}) diff --git a/client/src/components/atoms/Markdown.test.tsx b/client/src/components/atoms/Markdown.test.tsx new file mode 100644 index 0000000..2a48656 --- /dev/null +++ b/client/src/components/atoms/Markdown.test.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import { render } from 'react-testing-library' +import Markdown from './Markdown' + +describe('Markdown', () => { + it('renders without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) +}) diff --git a/client/src/components/atoms/Markdown.tsx b/client/src/components/atoms/Markdown.tsx index b9b92f6..0a51726 100644 --- a/client/src/components/atoms/Markdown.tsx +++ b/client/src/components/atoms/Markdown.tsx @@ -1,7 +1,7 @@ import React from 'react' import ReactMarkdown from 'react-markdown' -const Description = ({ +const Markdown = ({ text, className }: { @@ -15,4 +15,4 @@ const Description = ({ return } -export default Description +export default Markdown diff --git a/client/src/components/molecules/AccountStatus/Popover.test.tsx b/client/src/components/molecules/AccountStatus/Popover.test.tsx new file mode 100644 index 0000000..8e6b2a9 --- /dev/null +++ b/client/src/components/molecules/AccountStatus/Popover.test.tsx @@ -0,0 +1,49 @@ +import React from 'react' +import { render } from 'react-testing-library' +import Popover from './Popover' +import { userMock, userMockConnected } from '../../../../__mocks__/user-mock' +import { User } from '../../../context' + +describe('Popover', () => { + it('renders without crashing', () => { + const { container } = render( + + null} style={{}} /> + + ) + expect(container.firstChild).toBeInTheDocument() + }) + + it('renders connected without crashing', () => { + const { container } = render( + + null} style={{}} /> + + ) + expect(container.firstChild).toBeInTheDocument() + }) + + it('renders correct network', () => { + const { container } = render( + + null} style={{}} /> + + ) + expect(container.firstChild).toBeInTheDocument() + expect(container.firstChild).toHaveTextContent('Connected to Nile') + }) + + it('renders with wrong network', () => { + const { container } = render( + + null} style={{}} /> + + ) + expect(container.firstChild).toBeInTheDocument() + expect(container.firstChild).toHaveTextContent( + 'Please connect to Custom RPC' + ) + }) +}) diff --git a/client/src/components/molecules/AccountStatus/Popover.tsx b/client/src/components/molecules/AccountStatus/Popover.tsx index 040a744..ea18d02 100644 --- a/client/src/components/molecules/AccountStatus/Popover.tsx +++ b/client/src/components/molecules/AccountStatus/Popover.tsx @@ -9,6 +9,7 @@ export default class Popover extends PureComponent<{ }> { public render() { const { account, balance, network, isWeb3, isNile } = this.context + return (
{ + it('renders without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) + + it('togglePopover fires', () => { + const { container } = render() + + const indicator = container.querySelector('.statusIndicator') + + indicator && fireEvent.mouseOver(indicator) + expect(container.querySelector('.popover')).toBeInTheDocument() + indicator && fireEvent.mouseOut(indicator) + }) +}) diff --git a/client/src/components/molecules/AccountStatus/index.tsx b/client/src/components/molecules/AccountStatus/index.tsx index 8c420bb..91fdd09 100644 --- a/client/src/components/molecules/AccountStatus/index.tsx +++ b/client/src/components/molecules/AccountStatus/index.tsx @@ -19,7 +19,7 @@ export default class AccountStatus extends PureComponent< isPopoverOpen: false } - public togglePopover() { + private togglePopover() { this.setState(prevState => ({ isPopoverOpen: !prevState.isPopoverOpen })) diff --git a/client/src/components/molecules/Pagination.test.tsx b/client/src/components/molecules/Pagination.test.tsx index c93ec93..9d0b51b 100644 --- a/client/src/components/molecules/Pagination.test.tsx +++ b/client/src/components/molecules/Pagination.test.tsx @@ -2,7 +2,7 @@ import React from 'react' import { render } from 'react-testing-library' import Pagination from './Pagination' -describe('Button', () => { +describe('Pagination', () => { it('renders without crashing', () => { const { container } = render( { - if (item.web3 && !isWeb3) return null - - return ( - - {item.title} - - ) -} +const MenuItem = ({ item }: { item: any }) => ( + + {item.title} + +) export default class Header extends PureComponent { public render() { - const { isWeb3 } = this.context - return (
@@ -37,11 +31,7 @@ export default class Header extends PureComponent { diff --git a/client/src/components/organisms/Web3message.test.tsx b/client/src/components/organisms/Web3message.test.tsx index ca0384c..e38e7c8 100644 --- a/client/src/components/organisms/Web3message.test.tsx +++ b/client/src/components/organisms/Web3message.test.tsx @@ -1,11 +1,62 @@ import React from 'react' -import { render } from 'react-testing-library' +import { render, fireEvent } from 'react-testing-library' import Web3message from './Web3message' +import { User } from '../../context' +import { userMock, userMockConnected } from '../../../__mocks__/user-mock' describe('Web3message', () => { - it('default renders without crashing', () => { - const { container } = render() + it('renders with noWeb3 message', () => { + const { container } = render( + + + + ) + expect(container.firstChild).toHaveTextContent('Not a Web3 Browser') + }) - expect(container.firstChild).toBeInTheDocument() + it('renders with wrongNetwork message', () => { + const { container } = render( + + + + ) + expect(container.firstChild).toHaveTextContent( + 'Not connected to Nile network' + ) + }) + + it('renders with noAccount message', () => { + const { container } = render( + + + + ) + expect(container.firstChild).toHaveTextContent('No accounts detected') + }) + + it('renders with hasAccount message', () => { + const { container } = render( + + + + ) + expect(container.firstChild).toHaveTextContent('0xxxxxx') + }) + + it('button click fires unlockAccounts', () => { + const { getByText } = render( + + + + ) + + fireEvent.click(getByText('Unlock Account')) + expect(userMock.unlockAccounts).toBeCalled() }) }) diff --git a/client/src/components/templates/Route.test.tsx b/client/src/components/templates/Route.test.tsx new file mode 100644 index 0000000..014c0d2 --- /dev/null +++ b/client/src/components/templates/Route.test.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import { render } from 'react-testing-library' +import Route from './Route' + +describe('Route', () => { + it('renders without crashing', () => { + const { container } = render(Hello) + expect(container.firstChild).toBeInTheDocument() + }) + + it('renders title & description', () => { + const { container } = render( + + Hello + + ) + expect(container.querySelector('.title')).toHaveTextContent( + 'Hello Title' + ) + expect(container.querySelector('.description')).toHaveTextContent( + 'Hello Description' + ) + }) +}) diff --git a/client/src/context/UserProvider.tsx b/client/src/context/UserProvider.tsx index 335c6b4..f6c1dff 100644 --- a/client/src/context/UserProvider.tsx +++ b/client/src/context/UserProvider.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react' import Web3 from 'web3' -import { Logger } from '@oceanprotocol/squid' +import { Logger, Ocean, Account } from '@oceanprotocol/squid' import { User } from '.' import { provideOcean, requestFromFaucet, FaucetResponse } from '../ocean' import { nodeHost, nodePort, nodeScheme } from '../config' @@ -54,7 +54,7 @@ interface UserProviderState { } network: string web3: Web3 - ocean: any + ocean: Ocean requestFromFaucet(account: string): Promise unlockAccounts(): Promise message: string @@ -117,7 +117,7 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> { } } - private getWeb3 = async () => { + private getWeb3 = () => { // Modern dapp browsers if (window.ethereum) { window.web3 = new Web3(window.ethereum) @@ -236,7 +236,7 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> { } } - private fetchBalance = async (account: any) => { + private fetchBalance = async (account: Account) => { const balance = await account.getBalance() const { eth, ocn } = balance if (eth !== this.state.balance.eth || ocn !== this.state.balance.ocn) { diff --git a/client/src/data/web3message.json b/client/src/data/web3message.json index db9dc81..a441d40 100644 --- a/client/src/data/web3message.json +++ b/client/src/data/web3message.json @@ -1,6 +1,6 @@ { "noweb3": "Not a Web3 Browser. For publishing and downloading an asset you need to setup MetaMask or use any other Web3-capable plugin or browser.", "noAccount": "No accounts detected. For publishing and downloading an asset you need to unlock your Web3 account.", - "hasAccount": "Connected with account ", + "hasAccount": "", "wrongNetwork": "Not connected to Nile network.
Please connect in MetaMask with Custom RPC https://nile.dev-ocean.com" } diff --git a/client/src/hoc/withTracker.tsx b/client/src/hoc/withTracker.tsx index f976590..f5a74b2 100644 --- a/client/src/hoc/withTracker.tsx +++ b/client/src/hoc/withTracker.tsx @@ -8,7 +8,7 @@ const withTracker =

( options: FieldsObject = {} ) => { ReactGA.initialize(analyticsId, { - testMode: process.env.NODE_ENV === 'development', + testMode: process.env.NODE_ENV === 'test', debug: false }) diff --git a/client/src/index.test.tsx b/client/src/index.test.tsx new file mode 100644 index 0000000..4bf2b26 --- /dev/null +++ b/client/src/index.test.tsx @@ -0,0 +1,22 @@ +import ReactDOM from 'react-dom' +import { renderToDOM } from './index' + +describe('test ReactDOM.render', () => { + const originalRender = ReactDOM.render + const originalGetElement = global.document.getElementById + + beforeEach(() => { + global.document.getElementById = () => true + ReactDOM.render = jest.fn() + }) + + afterAll(() => { + global.document.getElementById = originalGetElement + ReactDOM.render = originalRender + }) + + it('should call ReactDOM.render', () => { + renderToDOM() + expect(ReactDOM.render).toHaveBeenCalled() + }) +}) diff --git a/client/src/index.tsx b/client/src/index.tsx index 9dd7ba7..54596d0 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,9 +1,25 @@ import React from 'react' import ReactDOM from 'react-dom' +import UserProvider from './context/UserProvider' import App from './App' import * as serviceWorker from './serviceWorker' -ReactDOM.render(, document.getElementById('root')) +function renderToDOM() { + const root = document.getElementById('root') + + if (root !== null) { + ReactDOM.render( + + + , + root + ) + } +} + +export { renderToDOM } + +renderToDOM() // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. diff --git a/client/src/ocean.test.ts b/client/src/ocean.test.ts new file mode 100644 index 0000000..2628ee6 --- /dev/null +++ b/client/src/ocean.test.ts @@ -0,0 +1,17 @@ +import Web3 from 'web3' +import { provideOcean, requestFromFaucet } from './ocean' + +describe('ocean', () => { + const web3 = new Web3(Web3.givenProvider) + + it('provideOcean can be called', async () => { + const response = await provideOcean(web3) + expect(response.ocean).toBeTruthy() + }) + + it('requestFromFaucet can be called', async () => { + const response = await requestFromFaucet('0xxxxxx') + response && + expect(response.errors[0].msg).toBe('Invalid Ethereum address') + }) +}) diff --git a/client/src/ocean.ts b/client/src/ocean.ts index e752aec..511788a 100644 --- a/client/src/ocean.ts +++ b/client/src/ocean.ts @@ -72,6 +72,6 @@ export async function requestFromFaucet(account: string) { }) return response.json() } catch (error) { - Logger.log('requestFromFaucet', error) + Logger.error('requestFromFaucet', error) } } diff --git a/client/src/routes/About.test.tsx b/client/src/routes/About.test.tsx new file mode 100644 index 0000000..0b1f795 --- /dev/null +++ b/client/src/routes/About.test.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import { render } from 'react-testing-library' +import About from './About' + +describe('About', () => { + it('renders without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) +}) diff --git a/client/src/routes/Details/AssetDetails.test.tsx b/client/src/routes/Details/AssetDetails.test.tsx new file mode 100644 index 0000000..8de750c --- /dev/null +++ b/client/src/routes/Details/AssetDetails.test.tsx @@ -0,0 +1,68 @@ +import React from 'react' +import { render } from 'react-testing-library' +import { DDO, MetaData } from '@oceanprotocol/squid' +import { BrowserRouter as Router } from 'react-router-dom' +import AssetDetails, { datafilesLine } from './AssetDetails' + +/* eslint-disable @typescript-eslint/no-explicit-any */ +describe('AssetDetails', () => { + it('renders loading without crashing', () => { + const { container } = render( + + ) + expect(container.firstChild).toBeInTheDocument() + }) + + it('renders with data', () => { + const { container } = render( + + + + ) + expect(container.querySelector('.description')).toHaveTextContent( + 'Description' + ) + expect(container.firstChild).toHaveTextContent('Category') + }) + + it('datafilesLine renders correctly for one file', () => { + const files = [ + { + index: 0, + url: 'https://hello.com' + } + ] + const { container } = render(datafilesLine(files)) + expect(container.firstChild).toHaveTextContent('1 data file') + }) + + it('datafilesLine renders correctly for multiple files', () => { + const files = [ + { + index: 0, + url: 'https://hello.com' + }, + { + index: 1, + url: 'https://hello2.com' + } + ] + const { container } = render(datafilesLine(files)) + expect(container.firstChild).toHaveTextContent('2 data files') + }) +}) diff --git a/client/src/routes/Details/AssetDetails.tsx b/client/src/routes/Details/AssetDetails.tsx index 1c418ac..655cd9c 100644 --- a/client/src/routes/Details/AssetDetails.tsx +++ b/client/src/routes/Details/AssetDetails.tsx @@ -1,23 +1,24 @@ import React, { PureComponent } from 'react' import { Link } from 'react-router-dom' import Moment from 'react-moment' +import { DDO, MetaData, File } from '@oceanprotocol/squid' import Markdown from '../../components/atoms/Markdown' import styles from './AssetDetails.module.scss' import AssetFilesDetails from './AssetFilesDetails' interface AssetDetailsProps { - metadata: any - ddo: any + metadata: MetaData + ddo: DDO +} + +export function datafilesLine(files: File[]) { + if (files.length === 1) { + return {files.length} data file + } + return {files.length} data files } export default class AssetDetails extends PureComponent { - private datafilesLine = (files: any) => { - if (files.length === 1) { - return {files.length} data file - } - return {files.length} data files - } - public render() { const { metadata, ddo } = this.props const { base } = metadata @@ -51,14 +52,16 @@ export default class AssetDetails extends PureComponent { )} - {base.files && this.datafilesLine(base.files)} + {base.files && datafilesLine(base.files)}

- + {base.description && ( + + )}
  • diff --git a/client/src/routes/Details/AssetFile.test.tsx b/client/src/routes/Details/AssetFile.test.tsx new file mode 100644 index 0000000..576e154 --- /dev/null +++ b/client/src/routes/Details/AssetFile.test.tsx @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import React from 'react' +import { render, fireEvent } from 'react-testing-library' +import { DDO } from '@oceanprotocol/squid' +import { StateMock } from '@react-mock/state' +import { User } from '../../context' +import AssetFile from './AssetFile' + +const file = { + index: 0, + url: 'https://hello.com', + contentType: 'zip', + contentLength: 100 +} + +const ddo = ({ id: 'xxx' } as any) as DDO + +const contextConnectedMock = { + isLogged: true, + isLoading: false, + isWeb3: true, + isNile: true, + account: '', + web3: {}, + ocean: {}, + balance: { eth: 0, ocn: 0 }, + network: '', + requestFromFaucet: () => {}, + unlockAccounts: () => {}, + message: '' +} + +describe('AssetFile', () => { + it('renders without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) + + it('button to be disabled when not connected', () => { + const { container } = render() + expect(container.querySelector('button')).toHaveAttribute('disabled') + }) + + it('button to be enabled when connected', async () => { + const { getByText } = render( + + + + ) + const button = getByText('Get file') + expect(button).not.toHaveAttribute('disabled') + + fireEvent.click(button) + }) + + it('renders loading state', async () => { + const { container } = render( + + + + ) + expect(container.querySelector('.spinner')).toBeInTheDocument() + }) + + it('renders error', async () => { + const { container } = render( + + + + ) + expect(container.querySelector('.error')).toBeInTheDocument() + expect(container.querySelector('.error')).toHaveTextContent( + 'Hello Error' + ) + }) +}) diff --git a/client/src/routes/Details/AssetFile.tsx b/client/src/routes/Details/AssetFile.tsx index 32f7374..a67f2d1 100644 --- a/client/src/routes/Details/AssetFile.tsx +++ b/client/src/routes/Details/AssetFile.tsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react' -import { Logger } from '@oceanprotocol/squid' +import { Logger, DDO, File } from '@oceanprotocol/squid' import filesize from 'filesize' import Button from '../../components/atoms/Button' import Spinner from '../../components/atoms/Spinner' @@ -8,8 +8,8 @@ import styles from './AssetFile.module.scss' import ReactGA from 'react-ga' interface AssetFileProps { - file: any - ddo: any + file: File + ddo: DDO } interface AssetFileState { @@ -30,7 +30,7 @@ export default class AssetFile extends PureComponent< private resetState = () => this.setState({ isLoading: true, error: '' }) - private purchaseAsset = async (ddo: any, index: number) => { + private purchaseAsset = async (ddo: DDO, index: number) => { this.resetState() ReactGA.event({ @@ -77,6 +77,7 @@ export default class AssetFile extends PureComponent< const { ddo, file } = this.props const { isLoading, message, error } = this.state const { isLogged, isNile } = this.context + const { index } = file return (
    @@ -97,7 +98,7 @@ export default class AssetFile extends PureComponent<