mirror of
https://github.com/oceanprotocol/commons.git
synced 2023-03-15 18:03:00 +01:00
Merge branch 'master' into feature/change-metadata
This commit is contained in:
commit
8cb13287db
@ -3,9 +3,14 @@ language: node_js
|
|||||||
node_js:
|
node_js:
|
||||||
- '11'
|
- '11'
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- npm install -g npm
|
||||||
|
- npm install -g codacy-coverage
|
||||||
|
|
||||||
script:
|
script:
|
||||||
# - ./scripts/install.sh # runs automatically with npm ci
|
# - ./scripts/install.sh # runs automatically with npm ci
|
||||||
- ./scripts/test.sh
|
- ./scripts/test.sh
|
||||||
|
- ./scripts/coverage.sh
|
||||||
- ./scripts/build.sh
|
- ./scripts/build.sh
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
@ -14,8 +19,6 @@ notifications:
|
|||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- node_modules
|
- node_modules
|
||||||
- client/node_modules
|
|
||||||
- server/node_modules
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
- provider: script
|
- provider: script
|
||||||
|
66
CHANGELOG.md
66
CHANGELOG.md
@ -4,12 +4,78 @@ All notable changes to this project will be documented in this file. Dates are d
|
|||||||
|
|
||||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
#### [v0.2.11](https://github.com/oceanprotocol/commons/compare/v0.2.10...v0.2.11)
|
||||||
|
|
||||||
|
> 16 May 2019
|
||||||
|
|
||||||
|
- url encode & decode search term [`#130`](https://github.com/oceanprotocol/commons/pull/130)
|
||||||
|
- fix react-ga initialization [`#129`](https://github.com/oceanprotocol/commons/pull/129)
|
||||||
|
- update changelog [`d49cace`](https://github.com/oceanprotocol/commons/commit/d49cace56a0aa551e26ad4eac2ecd196dcc499af)
|
||||||
|
- Release 0.2.11 [`e8b150a`](https://github.com/oceanprotocol/commons/commit/e8b150a8cb5d5fc3bae8c4684cfa73494752fb6d)
|
||||||
|
|
||||||
|
#### [v0.2.10](https://github.com/oceanprotocol/commons/compare/v0.2.9...v0.2.10)
|
||||||
|
|
||||||
|
> 15 May 2019
|
||||||
|
|
||||||
|
- AI for Good category [`#126`](https://github.com/oceanprotocol/commons/pull/126)
|
||||||
|
- Network detection tweaks [`#122`](https://github.com/oceanprotocol/commons/pull/122)
|
||||||
|
- bump squid-js [`97b20b4`](https://github.com/oceanprotocol/commons/commit/97b20b4e9b09f3ade7d55636fb6ef169bacf4222)
|
||||||
|
- isCorrectNetwork -> isOceanNetwork [`807723b`](https://github.com/oceanprotocol/commons/commit/807723be350a60da97f25c53eed102bafcbf1c3c)
|
||||||
|
- less verbose error logging [`f49fa14`](https://github.com/oceanprotocol/commons/commit/f49fa14b2aa7d35a91f8003ddd297b4f2df8ca78)
|
||||||
|
|
||||||
|
#### [v0.2.9](https://github.com/oceanprotocol/commons/compare/v0.2.8...v0.2.9)
|
||||||
|
|
||||||
|
> 13 May 2019
|
||||||
|
|
||||||
|
- add Duero to network detection [`#121`](https://github.com/oceanprotocol/commons/pull/121)
|
||||||
|
- add duero to network detection [`f636170`](https://github.com/oceanprotocol/commons/commit/f636170e3cfede29ae53576ad2f56c75b3d47655)
|
||||||
|
- update changelog [`70deb12`](https://github.com/oceanprotocol/commons/commit/70deb126b8f7d2867d6c04c179ea18d9f24d17d6)
|
||||||
|
- Release 0.2.9 [`57ea109`](https://github.com/oceanprotocol/commons/commit/57ea109ec29f2e2653f2121933ea4487adeb109b)
|
||||||
|
|
||||||
|
#### [v0.2.8](https://github.com/oceanprotocol/commons/compare/v0.2.7...v0.2.8)
|
||||||
|
|
||||||
|
> 10 May 2019
|
||||||
|
|
||||||
|
- Fix publish step 2 [`#120`](https://github.com/oceanprotocol/commons/pull/120)
|
||||||
|
- update changelog [`57b7547`](https://github.com/oceanprotocol/commons/commit/57b7547a21c233da92a959b426d4e6def90f3806)
|
||||||
|
- pass through all props to textarea [`90c252c`](https://github.com/oceanprotocol/commons/commit/90c252c055c4b162afa13e6bc9493de226dd8610)
|
||||||
|
- Release 0.2.8 [`784d6a1`](https://github.com/oceanprotocol/commons/commit/784d6a111fc449a7a2dcace4c06c493f927d5b44)
|
||||||
|
|
||||||
|
#### [v0.2.7](https://github.com/oceanprotocol/commons/compare/v0.2.6...v0.2.7)
|
||||||
|
|
||||||
|
> 6 May 2019
|
||||||
|
|
||||||
|
- Add more tests [`#117`](https://github.com/oceanprotocol/commons/pull/117)
|
||||||
|
- more tests [`9bdfca4`](https://github.com/oceanprotocol/commons/commit/9bdfca4be4bc4568035f5335ea997413c699d22a)
|
||||||
|
- update changelog [`30d6679`](https://github.com/oceanprotocol/commons/commit/30d6679eb2ae1cb40f8deb053b49d5a6fb71f089)
|
||||||
|
- consume fix [`20299d9`](https://github.com/oceanprotocol/commons/commit/20299d91164b3dc7b4b1c7183bf34d8ca42ea201)
|
||||||
|
|
||||||
|
#### [v0.2.6](https://github.com/oceanprotocol/commons/compare/v0.2.5...v0.2.6)
|
||||||
|
|
||||||
|
> 30 April 2019
|
||||||
|
|
||||||
|
- add blockies for account display [`#115`](https://github.com/oceanprotocol/commons/pull/115)
|
||||||
|
- Setup coverage reporting [`#116`](https://github.com/oceanprotocol/commons/pull/116)
|
||||||
|
- report test coverage to Codacy [`5e8ed02`](https://github.com/oceanprotocol/commons/commit/5e8ed026d556e98471fb9e0fbb5b680955e0dc8a)
|
||||||
|
- hotfix for search input [`a8969bb`](https://github.com/oceanprotocol/commons/commit/a8969bb70c0f206ea38c969d3b22706d8a9af29d)
|
||||||
|
- Release 0.2.6 [`269acdf`](https://github.com/oceanprotocol/commons/commit/269acdfc8fc106090232b0fa9716d04c7e195062)
|
||||||
|
|
||||||
|
#### [v0.2.5](https://github.com/oceanprotocol/commons/compare/v0.2.4...v0.2.5)
|
||||||
|
|
||||||
|
> 29 April 2019
|
||||||
|
|
||||||
|
- sanitize search input [`#114`](https://github.com/oceanprotocol/commons/pull/114)
|
||||||
|
- update telegram link [`#113`](https://github.com/oceanprotocol/commons/pull/113)
|
||||||
|
- update changelog [`0d3afc8`](https://github.com/oceanprotocol/commons/commit/0d3afc821900fada63013fc8aec434a634c67199)
|
||||||
|
- Release 0.2.5 [`8290609`](https://github.com/oceanprotocol/commons/commit/82906093e60f4b52302b6f08f19787f65b24fafd)
|
||||||
|
|
||||||
#### [v0.2.4](https://github.com/oceanprotocol/commons/compare/v0.2.3...v0.2.4)
|
#### [v0.2.4](https://github.com/oceanprotocol/commons/compare/v0.2.3...v0.2.4)
|
||||||
|
|
||||||
> 29 April 2019
|
> 29 April 2019
|
||||||
|
|
||||||
- fix search titles [`#112`](https://github.com/oceanprotocol/commons/pull/112)
|
- fix search titles [`#112`](https://github.com/oceanprotocol/commons/pull/112)
|
||||||
- Release 0.2.4 [`8f832b6`](https://github.com/oceanprotocol/commons/commit/8f832b63220a46a8de0061ffc2498186e2099315)
|
- Release 0.2.4 [`8f832b6`](https://github.com/oceanprotocol/commons/commit/8f832b63220a46a8de0061ffc2498186e2099315)
|
||||||
|
- update telegram link [`e6806f4`](https://github.com/oceanprotocol/commons/commit/e6806f467d6e2299bc0886eba53d0646dfa4df31)
|
||||||
|
|
||||||
#### [v0.2.3](https://github.com/oceanprotocol/commons/compare/v0.2.2...v0.2.3)
|
#### [v0.2.3](https://github.com/oceanprotocol/commons/compare/v0.2.2...v0.2.3)
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
> https://commons.oceanprotocol.com
|
> https://commons.oceanprotocol.com
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.com/oceanprotocol/commons.svg?branch=master)](https://travis-ci.com/oceanprotocol/commons)
|
[![Build Status](https://travis-ci.com/oceanprotocol/commons.svg?branch=master)](https://travis-ci.com/oceanprotocol/commons)
|
||||||
|
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/6a19987e62344b1c9c1d5bc9f315c733)](https://www.codacy.com/app/ocean-protocol/commons)
|
||||||
|
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/6a19987e62344b1c9c1d5bc9f315c733)](https://www.codacy.com/app/ocean-protocol/commons)
|
||||||
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-7b1173.svg?style=flat-square)](https://github.com/prettier/prettier)
|
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-7b1173.svg?style=flat-square)](https://github.com/prettier/prettier)
|
||||||
[![js oceanprotocol](https://img.shields.io/badge/js-oceanprotocol-7b1173.svg)](https://github.com/oceanprotocol/eslint-config-oceanprotocol)
|
[![js oceanprotocol](https://img.shields.io/badge/js-oceanprotocol-7b1173.svg)](https://github.com/oceanprotocol/eslint-config-oceanprotocol)
|
||||||
[![css bigchaindb](https://img.shields.io/badge/css-bigchaindb-39BA91.svg)](https://github.com/bigchaindb/stylelint-config-bigchaindb)
|
[![css bigchaindb](https://img.shields.io/badge/css-bigchaindb-39BA91.svg)](https://github.com/bigchaindb/stylelint-config-bigchaindb)
|
||||||
|
31
client/__mocks__/user-mock.ts
Normal file
31
client/__mocks__/user-mock.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const userMock = {
|
||||||
|
isLogged: false,
|
||||||
|
isLoading: false,
|
||||||
|
isWeb3: false,
|
||||||
|
isOceanNetwork: 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,
|
||||||
|
isOceanNetwork: true,
|
||||||
|
account: '0xxxxxx',
|
||||||
|
web3: {},
|
||||||
|
ocean: {},
|
||||||
|
balance: { eth: 0, ocn: 0 },
|
||||||
|
network: '',
|
||||||
|
requestFromFaucet: jest.fn(),
|
||||||
|
unlockAccounts: jest.fn(),
|
||||||
|
message: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export { userMock, userMockConnected }
|
2680
client/package-lock.json
generated
2680
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,27 +6,29 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts --max_old_space_size=4096 build",
|
"build": "react-scripts --max_old_space_size=4096 build",
|
||||||
"test": "react-scripts test --coverage",
|
"test": "react-scripts test --coverage --watchAll=false",
|
||||||
"test:watch": "react-scripts test --coverage --watch",
|
"test:watch": "react-scripts test --coverage",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject",
|
||||||
|
"coverage": "cat coverage/lcov.info | codacy-coverage --token 8801f827fe1144ffa85cd7da94f2bbf7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oceanprotocol/art": "^2.2.0",
|
"@oceanprotocol/art": "^2.2.0",
|
||||||
"@oceanprotocol/squid": "^0.5.7",
|
"@oceanprotocol/squid": "^0.5.10",
|
||||||
"@oceanprotocol/typographies": "^0.1.0",
|
"@oceanprotocol/typographies": "^0.1.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
"ethereum-blockies": "MyEtherWallet/blockies",
|
||||||
"filesize": "^4.1.2",
|
"filesize": "^4.1.2",
|
||||||
"history": "^4.9.0",
|
"history": "^4.9.0",
|
||||||
"is-url": "^1.2.4",
|
"is-url": "^1.2.4",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"query-string": "^6.4.2",
|
"query-string": "^6.5.0",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-datepicker": "^2.3.0",
|
"react-datepicker": "^2.5.0",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
"react-dotdotdot": "^1.2.3",
|
"react-dotdotdot": "^1.3.0",
|
||||||
"react-ga": "^2.5.7",
|
"react-ga": "^2.5.7",
|
||||||
"react-helmet": "^5.2.0",
|
"react-helmet": "^5.2.1",
|
||||||
"react-markdown": "^4.0.6",
|
"react-markdown": "^4.0.8",
|
||||||
"react-moment": "^0.9.2",
|
"react-moment": "^0.9.2",
|
||||||
"react-paginate": "^6.3.0",
|
"react-paginate": "^6.3.0",
|
||||||
"react-popper": "^1.3.3",
|
"react-popper": "^1.3.3",
|
||||||
@ -36,24 +38,25 @@
|
|||||||
"web3": "1.0.0-beta.37"
|
"web3": "1.0.0-beta.37"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@react-mock/state": "^0.1.8",
|
||||||
"@types/classnames": "^2.2.7",
|
"@types/classnames": "^2.2.7",
|
||||||
"@types/filesize": "^4.1.0",
|
"@types/filesize": "^4.1.0",
|
||||||
"@types/is-url": "^1.2.28",
|
"@types/is-url": "^1.2.28",
|
||||||
"@types/jest": "^24.0.11",
|
"@types/jest": "^24.0.12",
|
||||||
"@types/react": "^16.8.13",
|
"@types/react": "^16.8.15",
|
||||||
"@types/react-datepicker": "^2.2.1",
|
"@types/react-datepicker": "^2.3.0",
|
||||||
"@types/react-dom": "^16.8.3",
|
"@types/react-dom": "^16.8.4",
|
||||||
"@types/react-dotdotdot": "^1.2.0",
|
"@types/react-dotdotdot": "^1.2.0",
|
||||||
"@types/react-helmet": "^5.0.8",
|
"@types/react-helmet": "^5.0.8",
|
||||||
"@types/react-paginate": "^6.2.1",
|
"@types/react-paginate": "^6.2.1",
|
||||||
"@types/react-router-dom": "^4.3.1",
|
"@types/react-router-dom": "^4.3.2",
|
||||||
"@types/react-transition-group": "^2.8.0",
|
"@types/react-transition-group": "^2.9.1",
|
||||||
"@types/web3": "^1.0.18",
|
"@types/web3": "^1.0.18",
|
||||||
"jest-dom": "^3.1.3",
|
"jest-dom": "^3.1.4",
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.12.0",
|
||||||
"react-scripts": "^3.0.0",
|
"react-scripts": "^3.0.0",
|
||||||
"react-testing-library": "^6.1.2",
|
"react-testing-library": "^7.0.0",
|
||||||
"typescript": "^3.4.3"
|
"typescript": "^3.4.5"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -64,5 +67,11 @@
|
|||||||
"not dead",
|
"not dead",
|
||||||
"not ie <= 11",
|
"not ie <= 11",
|
||||||
"not op_mini all"
|
"not op_mini all"
|
||||||
]
|
],
|
||||||
|
"jest": {
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"src/**/*.{ts,tsx}",
|
||||||
|
"!src/serviceWorker.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
5
client/src/@types/ethereum-blockies/index.d.ts
vendored
Normal file
5
client/src/@types/ethereum-blockies/index.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
declare module 'ethereum-blockies' {
|
||||||
|
export function toDataUrl(address: string): string
|
||||||
|
}
|
@ -1,10 +1,25 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from 'react-testing-library'
|
import { render } from 'react-testing-library'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
import { User } from './context'
|
||||||
|
import { userMock } from '../__mocks__/user-mock'
|
||||||
|
|
||||||
describe('App', () => {
|
describe('App', () => {
|
||||||
|
it('should be able to run tests', () => {
|
||||||
|
expect(1 + 2).toEqual(3)
|
||||||
|
})
|
||||||
|
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(<App />)
|
const { container } = render(<App />)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders loading state', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider value={{ ...userMock, isLoading: true }}>
|
||||||
|
<App />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.spinner')).toBeInTheDocument()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,6 @@ import Header from './components/organisms/Header'
|
|||||||
import Footer from './components/organisms/Footer'
|
import Footer from './components/organisms/Footer'
|
||||||
import Spinner from './components/atoms/Spinner'
|
import Spinner from './components/atoms/Spinner'
|
||||||
import { User } from './context'
|
import { User } from './context'
|
||||||
import UserProvider from './context/UserProvider'
|
|
||||||
import Routes from './Routes'
|
import Routes from './Routes'
|
||||||
import './styles/global.scss'
|
import './styles/global.scss'
|
||||||
import styles from './App.module.scss'
|
import styles from './App.module.scss'
|
||||||
@ -12,33 +11,27 @@ import styles from './App.module.scss'
|
|||||||
export default class App extends Component {
|
export default class App extends Component {
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<UserProvider>
|
<div className={styles.app}>
|
||||||
<div className={styles.app}>
|
<Router>
|
||||||
<Router>
|
<>
|
||||||
<>
|
<Header />
|
||||||
<Header />
|
|
||||||
|
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<User.Consumer>
|
{this.context.isLoading ? (
|
||||||
{states =>
|
<div className={styles.loader}>
|
||||||
states.isLoading ? (
|
<Spinner message={this.context.message} />
|
||||||
<div className={styles.loader}>
|
</div>
|
||||||
<Spinner
|
) : (
|
||||||
message={states.message}
|
<Routes />
|
||||||
/>
|
)}
|
||||||
</div>
|
</main>
|
||||||
) : (
|
|
||||||
<Routes />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</User.Consumer>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
</Router>
|
</Router>
|
||||||
</div>
|
</div>
|
||||||
</UserProvider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
App.contextType = User
|
||||||
|
15
client/src/Routes.test.tsx
Normal file
15
client/src/Routes.test.tsx
Normal file
@ -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(
|
||||||
|
<Router>
|
||||||
|
<Routes />
|
||||||
|
</Router>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
24
client/src/components/atoms/Account.module.scss
Normal file
24
client/src/components/atoms/Account.module.scss
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
|
.account {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-family: $font-family-monospace;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockies {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: $spacer / 3;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
28
client/src/components/atoms/Account.test.tsx
Normal file
28
client/src/components/atoms/Account.test.tsx
Normal file
@ -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(<Account account={'0xxxxxxxxxxxxxxx'} />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('outputs empty state without account', () => {
|
||||||
|
const { container } = render(<Account account={''} />)
|
||||||
|
expect(container.firstChild).toHaveTextContent('No account selected')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('outputs blockie img', () => {
|
||||||
|
const account = '0xxxxxxxxxxxxxxx'
|
||||||
|
const blockies = toDataUrl(account)
|
||||||
|
|
||||||
|
const { container } = render(<Account account={account} />)
|
||||||
|
expect(container.querySelector('.blockies')).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('.blockies')).toHaveAttribute(
|
||||||
|
'src',
|
||||||
|
blockies
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
19
client/src/components/atoms/Account.tsx
Normal file
19
client/src/components/atoms/Account.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Dotdotdot from 'react-dotdotdot'
|
||||||
|
import { toDataUrl } from 'ethereum-blockies'
|
||||||
|
import styles from './Account.module.scss'
|
||||||
|
|
||||||
|
const Account = ({ account }: { account: string }) => {
|
||||||
|
const blockies = account && toDataUrl(account)
|
||||||
|
|
||||||
|
return account && blockies ? (
|
||||||
|
<div className={styles.account}>
|
||||||
|
<img className={styles.blockies} src={blockies} alt="Blockies" />
|
||||||
|
<Dotdotdot clamp={1}>{account}</Dotdotdot>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<em>No account selected</em>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Account
|
@ -1,16 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from 'react-testing-library'
|
import { render } from 'react-testing-library'
|
||||||
import slugify from 'slugify'
|
|
||||||
import CategoryImage from './CategoryImage'
|
import CategoryImage from './CategoryImage'
|
||||||
import formPublish from '../../data/form-publish.json'
|
import formPublish from '../../data/form-publish.json'
|
||||||
|
|
||||||
describe('CategoryImage', () => {
|
describe('CategoryImage', () => {
|
||||||
it('renders fallback image', () => {
|
it('renders fallback image', () => {
|
||||||
const { container, getByTestId } = render(
|
const { container } = render(<CategoryImage category={''} />)
|
||||||
<CategoryImage data-testid="image" category={''} />
|
|
||||||
)
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
expect(getByTestId('image').style.backgroundImage).toMatch(
|
expect(container.firstChild.style.backgroundImage).toMatch(
|
||||||
/jellyfish-back/
|
/jellyfish-back/
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -21,13 +18,8 @@ describe('CategoryImage', () => {
|
|||||||
: []
|
: []
|
||||||
|
|
||||||
options.map((category: string) => {
|
options.map((category: string) => {
|
||||||
const { getByTestId } = render(
|
const { container } = render(<CategoryImage category={category} />)
|
||||||
<CategoryImage data-testid="image" category={category} />
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
)
|
|
||||||
expect(getByTestId('image')).toBeInTheDocument()
|
|
||||||
// expect(getByTestId('image').style.backgroundImage).toMatch(
|
|
||||||
// slugify(category, { lower: true })
|
|
||||||
// )
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -133,6 +133,8 @@ const categoryImageFile = (category: string) => {
|
|||||||
case 'Visual Arts & Design':
|
case 'Visual Arts & Design':
|
||||||
case 'visualart':
|
case 'visualart':
|
||||||
return visualart
|
return visualart
|
||||||
|
case 'AI for Good':
|
||||||
|
return dataofdata
|
||||||
default:
|
default:
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
@ -148,7 +150,6 @@ export default class CategoryImage extends PureComponent<{ category: string }> {
|
|||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${image})`
|
backgroundImage: `url(${image})`
|
||||||
}}
|
}}
|
||||||
{...this.props}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
29
client/src/components/atoms/Form/Form.test.tsx
Normal file
29
client/src/components/atoms/Form/Form.test.tsx
Normal file
@ -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(<Form>Hello</Form>)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders title & description when set', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Form title="Hello Title" description="Hello Description">
|
||||||
|
Hello
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.formTitle')).toHaveTextContent(
|
||||||
|
'Hello Title'
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.formDescription')).toHaveTextContent(
|
||||||
|
'Hello Description'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can switch to minimal', () => {
|
||||||
|
const { container } = render(<Form minimal>Hello</Form>)
|
||||||
|
expect(container.firstChild).toHaveClass('formMinimal')
|
||||||
|
})
|
||||||
|
})
|
90
client/src/components/atoms/Form/Input.test.tsx
Normal file
90
client/src/components/atoms/Form/Input.test.tsx
Normal file
@ -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(<Input name="my-input" label="My Input" />)
|
||||||
|
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(<Input name="my-input" label="My Input" />)
|
||||||
|
expect(container.querySelector('.input')).toHaveAttribute(
|
||||||
|
'type',
|
||||||
|
'text'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders search', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Input name="my-input" label="My Input" type="search" />
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.input')).toHaveAttribute(
|
||||||
|
'type',
|
||||||
|
'search'
|
||||||
|
)
|
||||||
|
expect(container.querySelector('label + div')).toHaveClass(
|
||||||
|
'inputWrapSearch'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders select', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Input
|
||||||
|
name="my-input"
|
||||||
|
label="My Input"
|
||||||
|
type="select"
|
||||||
|
options={['hello', 'hello2']}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('select')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders textarea', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Input name="my-input" label="My Input" type="textarea" rows={40} />
|
||||||
|
)
|
||||||
|
expect(container.querySelector('textarea')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders radios', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Input
|
||||||
|
name="my-input"
|
||||||
|
label="My Input"
|
||||||
|
type="radio"
|
||||||
|
options={['hello', 'hello2']}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('input[type=radio]')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders checkboxes', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Input
|
||||||
|
name="my-input"
|
||||||
|
label="My Input"
|
||||||
|
type="checkbox"
|
||||||
|
options={['hello', 'hello2']}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
container.querySelector('input[type=checkbox]')
|
||||||
|
).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders date picker', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Input name="my-input" label="My Input" type="date" />
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
container.querySelector('.react-datepicker-wrapper')
|
||||||
|
).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -60,9 +60,8 @@ export default class Input extends PureComponent<InputProps, InputState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleDateChange = (date: Date) => {
|
private handleDateChange = (date: Date) => {
|
||||||
this.setState({
|
this.setState({ dateCreated: date })
|
||||||
dateCreated: date
|
|
||||||
})
|
|
||||||
const event = {
|
const event = {
|
||||||
currentTarget: {
|
currentTarget: {
|
||||||
name: 'dateCreated',
|
name: 'dateCreated',
|
||||||
@ -174,6 +173,7 @@ export default class Input extends PureComponent<InputProps, InputState> {
|
|||||||
<InputGroup>
|
<InputGroup>
|
||||||
<input
|
<input
|
||||||
id={name}
|
id={name}
|
||||||
|
type={type || 'text'}
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
onFocus={this.toggleFocus}
|
onFocus={this.toggleFocus}
|
||||||
onBlur={this.toggleFocus}
|
onBlur={this.toggleFocus}
|
||||||
@ -184,6 +184,7 @@ export default class Input extends PureComponent<InputProps, InputState> {
|
|||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
id={name}
|
id={name}
|
||||||
|
type={type || 'text'}
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
onFocus={this.toggleFocus}
|
onFocus={this.toggleFocus}
|
||||||
onBlur={this.toggleFocus}
|
onBlur={this.toggleFocus}
|
||||||
|
10
client/src/components/atoms/Form/InputGroup.test.tsx
Normal file
10
client/src/components/atoms/Form/InputGroup.test.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import InputGroup from './InputGroup'
|
||||||
|
|
||||||
|
describe('InputGroup', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<InputGroup>Hello</InputGroup>)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
20
client/src/components/atoms/Form/Label.test.tsx
Normal file
20
client/src/components/atoms/Form/Label.test.tsx
Normal file
@ -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(<Label htmlFor="hello">Hello</Label>)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders required state', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Label required htmlFor="hello">
|
||||||
|
Hello
|
||||||
|
</Label>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toHaveAttribute('title', 'Required')
|
||||||
|
expect(container.firstChild).toHaveClass('required')
|
||||||
|
})
|
||||||
|
})
|
10
client/src/components/atoms/Form/Row.test.tsx
Normal file
10
client/src/components/atoms/Form/Row.test.tsx
Normal file
@ -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(<Row>Hello</Row>)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
10
client/src/components/atoms/Markdown.test.tsx
Normal file
10
client/src/components/atoms/Markdown.test.tsx
Normal file
@ -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(<Markdown text={'#hello'} />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
|
||||||
const Description = ({
|
const Markdown = ({
|
||||||
text,
|
text,
|
||||||
className
|
className
|
||||||
}: {
|
}: {
|
||||||
@ -15,4 +15,4 @@ const Description = ({
|
|||||||
return <ReactMarkdown source={textCleaned} className={className} />
|
return <ReactMarkdown source={textCleaned} className={className} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Description
|
export default Markdown
|
||||||
|
@ -22,7 +22,7 @@ const Indicator = ({
|
|||||||
{states =>
|
{states =>
|
||||||
!states.isWeb3 ? (
|
!states.isWeb3 ? (
|
||||||
<span className={styles.statusIndicator} />
|
<span className={styles.statusIndicator} />
|
||||||
) : !states.isLogged || !states.isNile ? (
|
) : !states.isLogged || !states.isOceanNetwork ? (
|
||||||
<span className={styles.statusIndicatorCloseEnough} />
|
<span className={styles.statusIndicatorCloseEnough} />
|
||||||
) : states.isLogged ? (
|
) : states.isLogged ? (
|
||||||
<span className={styles.statusIndicatorActive} />
|
<span className={styles.statusIndicatorActive} />
|
||||||
|
@ -45,14 +45,6 @@ $popoverWidth: 18rem;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.address {
|
|
||||||
display: block;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
font-family: $font-family-monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance {
|
.balance {
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
margin-left: $spacer / 2;
|
margin-left: $spacer / 2;
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
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(
|
||||||
|
<User.Provider value={userMock}>
|
||||||
|
<Popover forwardedRef={() => null} style={{}} />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders connected without crashing', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider value={userMockConnected}>
|
||||||
|
<Popover forwardedRef={() => null} style={{}} />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders correct network', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider value={{ ...userMockConnected, network: 'Nile' }}>
|
||||||
|
<Popover forwardedRef={() => null} style={{}} />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
expect(container.firstChild).toHaveTextContent('Connected to Nile')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with wrong network', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider
|
||||||
|
value={{
|
||||||
|
...userMockConnected,
|
||||||
|
isOceanNetwork: false,
|
||||||
|
network: '1'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Popover forwardedRef={() => null} style={{}} />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
expect(container.firstChild).toHaveTextContent(
|
||||||
|
'Please connect to Custom RPC'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Dotdotdot from 'react-dotdotdot'
|
import Account from '../../atoms/Account'
|
||||||
import { User } from '../../../context'
|
import { User } from '../../../context'
|
||||||
import styles from './Popover.module.scss'
|
import styles from './Popover.module.scss'
|
||||||
|
|
||||||
@ -8,30 +8,20 @@ export default class Popover extends PureComponent<{
|
|||||||
style: React.CSSProperties
|
style: React.CSSProperties
|
||||||
}> {
|
}> {
|
||||||
public render() {
|
public render() {
|
||||||
const { account, balance, network, isWeb3, isNile } = this.context
|
const {
|
||||||
|
account,
|
||||||
|
balance,
|
||||||
|
network,
|
||||||
|
isWeb3,
|
||||||
|
isOceanNetwork
|
||||||
|
} = this.context
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.popover}
|
className={styles.popover}
|
||||||
ref={this.props.forwardedRef}
|
ref={this.props.forwardedRef}
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
>
|
>
|
||||||
{account && balance && (
|
|
||||||
<div className={styles.popoverInfoline}>
|
|
||||||
<span
|
|
||||||
className={styles.balance}
|
|
||||||
title={(balance.eth / 1e18).toFixed(10)}
|
|
||||||
>
|
|
||||||
<strong>
|
|
||||||
{(balance.eth / 1e18).toFixed(3).slice(0, -1)}
|
|
||||||
</strong>{' '}
|
|
||||||
ETH
|
|
||||||
</span>
|
|
||||||
<span className={styles.balance}>
|
|
||||||
<strong>{balance.ocn}</strong> OCEAN
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isWeb3 ? (
|
{!isWeb3 ? (
|
||||||
<div className={styles.popoverInfoline}>
|
<div className={styles.popoverInfoline}>
|
||||||
No Web3 detected. Use a browser with MetaMask installed
|
No Web3 detected. Use a browser with MetaMask installed
|
||||||
@ -40,18 +30,30 @@ export default class Popover extends PureComponent<{
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className={styles.popoverInfoline}>
|
<div className={styles.popoverInfoline}>
|
||||||
{account ? (
|
<Account account={account} />
|
||||||
<Dotdotdot clamp={1}>
|
|
||||||
<span className={styles.address}>
|
|
||||||
{account}
|
|
||||||
</span>
|
|
||||||
</Dotdotdot>
|
|
||||||
) : (
|
|
||||||
<em>No account selected</em>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{account && balance && (
|
||||||
|
<div className={styles.popoverInfoline}>
|
||||||
|
<span
|
||||||
|
className={styles.balance}
|
||||||
|
title={(balance.eth / 1e18).toFixed(10)}
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
{(balance.eth / 1e18)
|
||||||
|
.toFixed(3)
|
||||||
|
.slice(0, -1)}
|
||||||
|
</strong>{' '}
|
||||||
|
ETH
|
||||||
|
</span>
|
||||||
|
<span className={styles.balance}>
|
||||||
|
<strong>{balance.ocn}</strong> OCEAN
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={styles.popoverInfoline}>
|
<div className={styles.popoverInfoline}>
|
||||||
{network && !isNile
|
{network && !isOceanNetwork
|
||||||
? 'Please connect to Custom RPC\n https://nile.dev-ocean.com'
|
? 'Please connect to Custom RPC\n https://nile.dev-ocean.com'
|
||||||
: network && `Connected to ${network} network`}
|
: network && `Connected to ${network} network`}
|
||||||
</div>
|
</div>
|
||||||
|
20
client/src/components/molecules/AccountStatus/index.test.tsx
Normal file
20
client/src/components/molecules/AccountStatus/index.test.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from 'react-testing-library'
|
||||||
|
import AccountStatus from '.'
|
||||||
|
|
||||||
|
describe('AccountStatus', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<AccountStatus />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('togglePopover fires', () => {
|
||||||
|
const { container } = render(<AccountStatus />)
|
||||||
|
|
||||||
|
const indicator = container.querySelector('.statusIndicator')
|
||||||
|
|
||||||
|
indicator && fireEvent.mouseOver(indicator)
|
||||||
|
expect(container.querySelector('.popover')).toBeInTheDocument()
|
||||||
|
indicator && fireEvent.mouseOut(indicator)
|
||||||
|
})
|
||||||
|
})
|
@ -19,7 +19,7 @@ export default class AccountStatus extends PureComponent<
|
|||||||
isPopoverOpen: false
|
isPopoverOpen: false
|
||||||
}
|
}
|
||||||
|
|
||||||
public togglePopover() {
|
private togglePopover() {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
isPopoverOpen: !prevState.isPopoverOpen
|
isPopoverOpen: !prevState.isPopoverOpen
|
||||||
}))
|
}))
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import { render } from 'react-testing-library'
|
import { render } from 'react-testing-library'
|
||||||
import Pagination from './Pagination'
|
import Pagination from './Pagination'
|
||||||
|
|
||||||
describe('Button', () => {
|
describe('Pagination', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<Pagination
|
<Pagination
|
||||||
|
@ -57,10 +57,10 @@ export default class AssetsUser extends PureComponent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { account, isNile } = this.context
|
const { account, isOceanNetwork } = this.context
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isNile &&
|
isOceanNetwork &&
|
||||||
account && (
|
account && (
|
||||||
<div className={styles.assetsUser}>
|
<div className={styles.assetsUser}>
|
||||||
{this.props.recent && (
|
{this.props.recent && (
|
||||||
|
@ -8,25 +8,19 @@ import styles from './Header.module.scss'
|
|||||||
import menu from '../../data/menu.json'
|
import menu from '../../data/menu.json'
|
||||||
import meta from '../../data/meta.json'
|
import meta from '../../data/meta.json'
|
||||||
|
|
||||||
const MenuItem = ({ item, isWeb3 }: { item: any; isWeb3: boolean }) => {
|
const MenuItem = ({ item }: { item: any }) => (
|
||||||
if (item.web3 && !isWeb3) return null
|
<NavLink
|
||||||
|
to={item.link}
|
||||||
return (
|
className={styles.link}
|
||||||
<NavLink
|
activeClassName={styles.linkActive}
|
||||||
to={item.link}
|
exact
|
||||||
className={styles.link}
|
>
|
||||||
activeClassName={styles.linkActive}
|
{item.title}
|
||||||
exact
|
</NavLink>
|
||||||
>
|
)
|
||||||
{item.title}
|
|
||||||
</NavLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Header extends PureComponent {
|
export default class Header extends PureComponent {
|
||||||
public render() {
|
public render() {
|
||||||
const { isWeb3 } = this.context
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<div className={styles.headerContent}>
|
<div className={styles.headerContent}>
|
||||||
@ -37,11 +31,7 @@ export default class Header extends PureComponent {
|
|||||||
|
|
||||||
<nav className={styles.headerMenu}>
|
<nav className={styles.headerMenu}>
|
||||||
{menu.map(item => (
|
{menu.map(item => (
|
||||||
<MenuItem
|
<MenuItem key={item.title} item={item} />
|
||||||
key={item.title}
|
|
||||||
item={item}
|
|
||||||
isWeb3={isWeb3}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
<AccountStatus className={styles.accountStatus} />
|
<AccountStatus className={styles.accountStatus} />
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -3,27 +3,20 @@
|
|||||||
.message {
|
.message {
|
||||||
margin-bottom: $spacer;
|
margin-bottom: $spacer;
|
||||||
color: $brand-grey;
|
color: $brand-grey;
|
||||||
padding-left: 2rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom: .1rem solid $brand-grey-lighter;
|
border-bottom: .1rem solid $brand-grey-lighter;
|
||||||
border-top: .1rem solid $brand-grey-lighter;
|
border-top: .1rem solid $brand-grey-lighter;
|
||||||
padding-top: $spacer / 2;
|
padding-top: $spacer / 2;
|
||||||
padding-bottom: $spacer / 2;
|
padding-bottom: $spacer / 2;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
> div {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.account {
|
.warnings {
|
||||||
display: inline-block;
|
padding-left: $spacer;
|
||||||
margin-left: $spacer / 8;
|
|
||||||
background: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
margin-left: -($spacer);
|
margin-left: -($spacer);
|
||||||
margin-right: $spacer / 3;
|
margin-right: $spacer / 2;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,64 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from 'react-testing-library'
|
import { render, fireEvent } from 'react-testing-library'
|
||||||
import Web3message from './Web3message'
|
import Web3message from './Web3message'
|
||||||
|
import { User } from '../../context'
|
||||||
|
import { userMock, userMockConnected } from '../../../__mocks__/user-mock'
|
||||||
|
|
||||||
describe('Web3message', () => {
|
describe('Web3message', () => {
|
||||||
it('default renders without crashing', () => {
|
it('renders with noWeb3 message', () => {
|
||||||
const { container } = render(<Web3message />)
|
const { container } = render(
|
||||||
|
<User.Provider value={{ ...userMock }}>
|
||||||
|
<Web3message />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toHaveTextContent('Not a Web3 Browser')
|
||||||
|
})
|
||||||
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
it('renders with wrongNetwork message', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider value={{ ...userMock, isWeb3: true }}>
|
||||||
|
<Web3message />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toHaveTextContent(
|
||||||
|
'Not connected to Nile network'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with noAccount message', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider
|
||||||
|
value={{ ...userMock, isWeb3: true, isOceanNetwork: true }}
|
||||||
|
>
|
||||||
|
<Web3message />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toHaveTextContent('No accounts detected')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with hasAccount message', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider value={userMockConnected}>
|
||||||
|
<Web3message />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toHaveTextContent('0xxxxxx')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('button click fires unlockAccounts', () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<User.Provider
|
||||||
|
value={{
|
||||||
|
...userMock,
|
||||||
|
isWeb3: true,
|
||||||
|
isOceanNetwork: true
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Web3message />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(getByText('Unlock Account'))
|
||||||
|
expect(userMock.unlockAccounts).toBeCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Dotdotdot from 'react-dotdotdot'
|
import Account from '../atoms/Account'
|
||||||
import Button from '../atoms/Button'
|
import Button from '../atoms/Button'
|
||||||
import AccountStatus from '../molecules/AccountStatus'
|
import AccountStatus from '../molecules/AccountStatus'
|
||||||
import styles from './Web3message.module.scss'
|
import styles from './Web3message.module.scss'
|
||||||
@ -13,21 +13,18 @@ export default class Web3message extends PureComponent {
|
|||||||
unlockAccounts?: () => any
|
unlockAccounts?: () => any
|
||||||
) => (
|
) => (
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
<AccountStatus className={styles.status} />{' '}
|
|
||||||
{account ? (
|
{account ? (
|
||||||
<Dotdotdot clamp={1}>
|
<Account account={account} />
|
||||||
{message}
|
|
||||||
<code className={styles.account}>{account}</code>
|
|
||||||
</Dotdotdot>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<div className={styles.warnings}>
|
||||||
|
<AccountStatus className={styles.status} />
|
||||||
<span dangerouslySetInnerHTML={{ __html: message }} />{' '}
|
<span dangerouslySetInnerHTML={{ __html: message }} />{' '}
|
||||||
{unlockAccounts && (
|
{unlockAccounts && (
|
||||||
<Button onClick={() => unlockAccounts()} link>
|
<Button onClick={() => unlockAccounts()} link>
|
||||||
Unlock Account
|
Unlock Account
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -35,7 +32,7 @@ export default class Web3message extends PureComponent {
|
|||||||
public render() {
|
public render() {
|
||||||
const {
|
const {
|
||||||
isWeb3,
|
isWeb3,
|
||||||
isNile,
|
isOceanNetwork,
|
||||||
isLogged,
|
isLogged,
|
||||||
account,
|
account,
|
||||||
unlockAccounts
|
unlockAccounts
|
||||||
@ -43,7 +40,7 @@ export default class Web3message extends PureComponent {
|
|||||||
|
|
||||||
return !isWeb3
|
return !isWeb3
|
||||||
? this.message(content.noweb3)
|
? this.message(content.noweb3)
|
||||||
: !isNile
|
: !isOceanNetwork
|
||||||
? this.message(content.wrongNetwork)
|
? this.message(content.wrongNetwork)
|
||||||
: !isLogged
|
: !isLogged
|
||||||
? this.message(content.noAccount, '', unlockAccounts)
|
? this.message(content.noAccount, '', unlockAccounts)
|
||||||
|
24
client/src/components/templates/Route.test.tsx
Normal file
24
client/src/components/templates/Route.test.tsx
Normal file
@ -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(<Route title="Hello Title">Hello</Route>)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders title & description', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Route title="Hello Title" description="Hello Description">
|
||||||
|
Hello
|
||||||
|
</Route>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.title')).toHaveTextContent(
|
||||||
|
'Hello Title'
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.description')).toHaveTextContent(
|
||||||
|
'Hello Description'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { Logger } from '@oceanprotocol/squid'
|
import { Logger, Ocean, Account } from '@oceanprotocol/squid'
|
||||||
import { User } from '.'
|
import { User } from '.'
|
||||||
import { provideOcean, requestFromFaucet, FaucetResponse } from '../ocean'
|
import { provideOcean, requestFromFaucet, FaucetResponse } from '../ocean'
|
||||||
import { nodeHost, nodePort, nodeScheme } from '../config'
|
import { nodeHost, nodePort, nodeScheme } from '../config'
|
||||||
@ -46,7 +46,7 @@ interface UserProviderState {
|
|||||||
isLogged: boolean
|
isLogged: boolean
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
isWeb3: boolean
|
isWeb3: boolean
|
||||||
isNile: boolean
|
isOceanNetwork: boolean
|
||||||
account: string
|
account: string
|
||||||
balance: {
|
balance: {
|
||||||
eth: number
|
eth: number
|
||||||
@ -54,7 +54,7 @@ interface UserProviderState {
|
|||||||
}
|
}
|
||||||
network: string
|
network: string
|
||||||
web3: Web3
|
web3: Web3
|
||||||
ocean: any
|
ocean: Ocean
|
||||||
requestFromFaucet(account: string): Promise<FaucetResponse>
|
requestFromFaucet(account: string): Promise<FaucetResponse>
|
||||||
unlockAccounts(): Promise<any>
|
unlockAccounts(): Promise<any>
|
||||||
message: string
|
message: string
|
||||||
@ -74,7 +74,7 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
isLogged: false,
|
isLogged: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
isWeb3: false,
|
isWeb3: false,
|
||||||
isNile: false,
|
isOceanNetwork: false,
|
||||||
balance: {
|
balance: {
|
||||||
eth: 0,
|
eth: 0,
|
||||||
ocn: 0
|
ocn: 0
|
||||||
@ -117,7 +117,7 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getWeb3 = async () => {
|
private getWeb3 = () => {
|
||||||
// Modern dapp browsers
|
// Modern dapp browsers
|
||||||
if (window.ethereum) {
|
if (window.ethereum) {
|
||||||
window.web3 = new Web3(window.ethereum)
|
window.web3 = new Web3(window.ethereum)
|
||||||
@ -151,23 +151,32 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
//
|
//
|
||||||
// Detecting network with window.web3
|
// Detecting network with window.web3
|
||||||
//
|
//
|
||||||
let isNile
|
let isOceanNetwork
|
||||||
|
|
||||||
await window.web3.eth.net.getId((err, netId) => {
|
await window.web3.eth.net.getId((err, netId) => {
|
||||||
if (err) return
|
if (err) return
|
||||||
|
|
||||||
isNile = netId === 8995
|
const isNile = netId === 8995
|
||||||
const network = isNile ? 'Nile' : netId.toString()
|
const isDuero = netId === 2199
|
||||||
|
const isSpree = netId === 8996
|
||||||
|
|
||||||
|
isOceanNetwork = isNile || isDuero || isSpree
|
||||||
|
|
||||||
|
const network = isNile
|
||||||
|
? 'Nile'
|
||||||
|
: isDuero
|
||||||
|
? 'Duero'
|
||||||
|
: netId.toString()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isNile !== this.state.isNile ||
|
isOceanNetwork !== this.state.isOceanNetwork ||
|
||||||
network !== this.state.network
|
network !== this.state.network
|
||||||
) {
|
) {
|
||||||
this.setState({ isNile, network })
|
this.setState({ isOceanNetwork, network })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!isNile) {
|
if (!isOceanNetwork) {
|
||||||
web3 = this.state.web3 // eslint-disable-line
|
web3 = this.state.web3 // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,19 +204,19 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// error in bootstrap process
|
// error in bootstrap process
|
||||||
// show error connecting to ocean
|
// show error connecting to ocean
|
||||||
Logger.log('web3 error', e)
|
Logger.error('web3 error', e.message)
|
||||||
this.setState({ isLoading: false })
|
this.setState({ isLoading: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchAccounts = async () => {
|
private fetchAccounts = async () => {
|
||||||
const { ocean, isWeb3, isLogged, isNile } = this.state
|
const { ocean, isWeb3, isLogged, isOceanNetwork } = this.state
|
||||||
|
|
||||||
if (isWeb3) {
|
if (isWeb3) {
|
||||||
let accounts
|
let accounts
|
||||||
|
|
||||||
// Modern dapp browsers
|
// Modern dapp browsers
|
||||||
if (window.ethereum && !isLogged && isNile) {
|
if (window.ethereum && !isLogged && isOceanNetwork) {
|
||||||
// simply set to empty, and have user click a button somewhere
|
// simply set to empty, and have user click a button somewhere
|
||||||
// to initiate account unlocking
|
// to initiate account unlocking
|
||||||
accounts = []
|
accounts = []
|
||||||
@ -236,7 +245,7 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchBalance = async (account: any) => {
|
private fetchBalance = async (account: Account) => {
|
||||||
const balance = await account.getBalance()
|
const balance = await account.getBalance()
|
||||||
const { eth, ocn } = balance
|
const { eth, ocn } = balance
|
||||||
if (eth !== this.state.balance.eth || ocn !== this.state.balance.ocn) {
|
if (eth !== this.state.balance.eth || ocn !== this.state.balance.ocn) {
|
||||||
@ -250,8 +259,12 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
if (isWeb3) {
|
if (isWeb3) {
|
||||||
const network = await ocean.keeper.getNetworkName()
|
const network = await ocean.keeper.getNetworkName()
|
||||||
const isNile = network === 'Nile'
|
const isNile = network === 'Nile'
|
||||||
|
const isDuero = network === 'Duero'
|
||||||
|
const isSpree = network === 'Spree'
|
||||||
|
const isOceanNetwork = isNile || isDuero || isSpree
|
||||||
|
|
||||||
network !== this.state.network && this.setState({ isNile, network })
|
network !== this.state.network &&
|
||||||
|
this.setState({ isOceanNetwork, network })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ export const User = React.createContext({
|
|||||||
isLogged: false,
|
isLogged: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isWeb3: false,
|
isWeb3: false,
|
||||||
isNile: false,
|
isOceanNetwork: false,
|
||||||
account: '',
|
account: '',
|
||||||
web3: {},
|
web3: {},
|
||||||
ocean: {},
|
ocean: {},
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
"type": "select",
|
"type": "select",
|
||||||
"required": true,
|
"required": true,
|
||||||
"options": [
|
"options": [
|
||||||
|
"AI for Good",
|
||||||
"Image Recognition",
|
"Image Recognition",
|
||||||
"Dataset Of Datasets",
|
"Dataset Of Datasets",
|
||||||
"Language",
|
"Language",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"noweb3": "Not a Web3 Browser. For publishing and downloading an asset you need to <a href='https://docs.oceanprotocol.com/tutorials/metamask-setup/' target='_blank' rel='noopener noreferrer'>setup MetaMask</a> or use any other Web3-capable plugin or browser.",
|
"noweb3": "Not a Web3 Browser. For publishing and downloading an asset you need to <a href='https://docs.oceanprotocol.com/tutorials/metamask-setup/' target='_blank' rel='noopener noreferrer'>setup MetaMask</a> 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.",
|
"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.<br />Please connect in MetaMask with Custom RPC <code>https://nile.dev-ocean.com</code>"
|
"wrongNetwork": "Not connected to Nile network.<br />Please connect in MetaMask with Custom RPC <code>https://nile.dev-ocean.com</code>"
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,15 @@ import ReactGA, { FieldsObject } from 'react-ga'
|
|||||||
import { RouteComponentProps } from 'react-router-dom'
|
import { RouteComponentProps } from 'react-router-dom'
|
||||||
import { analyticsId } from '../config'
|
import { analyticsId } from '../config'
|
||||||
|
|
||||||
|
ReactGA.initialize(analyticsId, {
|
||||||
|
testMode: process.env.NODE_ENV === 'test',
|
||||||
|
debug: false
|
||||||
|
})
|
||||||
|
|
||||||
const withTracker = <P extends RouteComponentProps>(
|
const withTracker = <P extends RouteComponentProps>(
|
||||||
WrappedComponent: any,
|
WrappedComponent: any,
|
||||||
options: FieldsObject = {}
|
options: FieldsObject = {}
|
||||||
) => {
|
) => {
|
||||||
ReactGA.initialize(analyticsId, {
|
|
||||||
testMode: process.env.NODE_ENV === 'development',
|
|
||||||
debug: false
|
|
||||||
})
|
|
||||||
|
|
||||||
const trackPage = (page: string) => {
|
const trackPage = (page: string) => {
|
||||||
options.isWeb3 = window.web3 !== undefined
|
options.isWeb3 = window.web3 !== undefined
|
||||||
|
|
||||||
|
22
client/src/index.test.tsx
Normal file
22
client/src/index.test.tsx
Normal file
@ -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()
|
||||||
|
})
|
||||||
|
})
|
@ -1,9 +1,25 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
|
import UserProvider from './context/UserProvider'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
import * as serviceWorker from './serviceWorker'
|
import * as serviceWorker from './serviceWorker'
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById('root'))
|
function renderToDOM() {
|
||||||
|
const root = document.getElementById('root')
|
||||||
|
|
||||||
|
if (root !== null) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<UserProvider>
|
||||||
|
<App />
|
||||||
|
</UserProvider>,
|
||||||
|
root
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { renderToDOM }
|
||||||
|
|
||||||
|
renderToDOM()
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
|
17
client/src/ocean.test.ts
Normal file
17
client/src/ocean.test.ts
Normal file
@ -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')
|
||||||
|
})
|
||||||
|
})
|
@ -72,6 +72,6 @@ export async function requestFromFaucet(account: string) {
|
|||||||
})
|
})
|
||||||
return response.json()
|
return response.json()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.log('requestFromFaucet', error)
|
Logger.error('requestFromFaucet', error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
client/src/routes/About.test.tsx
Normal file
10
client/src/routes/About.test.tsx
Normal file
@ -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(<About />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
68
client/src/routes/Details/AssetDetails.test.tsx
Normal file
68
client/src/routes/Details/AssetDetails.test.tsx
Normal file
@ -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(
|
||||||
|
<AssetDetails
|
||||||
|
metadata={({ base: { name: '' } } as any) as MetaData}
|
||||||
|
ddo={({} as any) as DDO}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with data', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Router>
|
||||||
|
<AssetDetails
|
||||||
|
metadata={
|
||||||
|
({
|
||||||
|
base: {
|
||||||
|
name: 'Hello',
|
||||||
|
description: 'Description',
|
||||||
|
categories: ['Category'],
|
||||||
|
files: [{ index: 0 }]
|
||||||
|
}
|
||||||
|
} as any) as MetaData
|
||||||
|
}
|
||||||
|
ddo={({} as any) as DDO}
|
||||||
|
/>
|
||||||
|
</Router>
|
||||||
|
)
|
||||||
|
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')
|
||||||
|
})
|
||||||
|
})
|
@ -1,23 +1,24 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import Moment from 'react-moment'
|
import Moment from 'react-moment'
|
||||||
|
import { DDO, MetaData, File } from '@oceanprotocol/squid'
|
||||||
import Markdown from '../../components/atoms/Markdown'
|
import Markdown from '../../components/atoms/Markdown'
|
||||||
import styles from './AssetDetails.module.scss'
|
import styles from './AssetDetails.module.scss'
|
||||||
import AssetFilesDetails from './AssetFilesDetails'
|
import AssetFilesDetails from './AssetFilesDetails'
|
||||||
|
|
||||||
interface AssetDetailsProps {
|
interface AssetDetailsProps {
|
||||||
metadata: any
|
metadata: MetaData
|
||||||
ddo: any
|
ddo: DDO
|
||||||
|
}
|
||||||
|
|
||||||
|
export function datafilesLine(files: File[]) {
|
||||||
|
if (files.length === 1) {
|
||||||
|
return <span>{files.length} data file</span>
|
||||||
|
}
|
||||||
|
return <span>{files.length} data files</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AssetDetails extends PureComponent<AssetDetailsProps> {
|
export default class AssetDetails extends PureComponent<AssetDetailsProps> {
|
||||||
private datafilesLine = (files: any) => {
|
|
||||||
if (files.length === 1) {
|
|
||||||
return <span>{files.length} data file</span>
|
|
||||||
}
|
|
||||||
return <span>{files.length} data files</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { metadata, ddo } = this.props
|
const { metadata, ddo } = this.props
|
||||||
const { base } = metadata
|
const { base } = metadata
|
||||||
@ -51,14 +52,16 @@ export default class AssetDetails extends PureComponent<AssetDetailsProps> {
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{base.files && this.datafilesLine(base.files)}
|
{base.files && datafilesLine(base.files)}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<Markdown
|
{base.description && (
|
||||||
text={base.description}
|
<Markdown
|
||||||
className={styles.description}
|
text={base.description}
|
||||||
/>
|
className={styles.description}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<ul className={styles.meta}>
|
<ul className={styles.meta}>
|
||||||
<li>
|
<li>
|
||||||
|
80
client/src/routes/Details/AssetFile.test.tsx
Normal file
80
client/src/routes/Details/AssetFile.test.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/* 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 ReactGA from 'react-ga'
|
||||||
|
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,
|
||||||
|
isOceanNetwork: true,
|
||||||
|
account: '',
|
||||||
|
web3: {},
|
||||||
|
ocean: {},
|
||||||
|
balance: { eth: 0, ocn: 0 },
|
||||||
|
network: '',
|
||||||
|
requestFromFaucet: () => {},
|
||||||
|
unlockAccounts: () => {},
|
||||||
|
message: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactGA.initialize('foo', { testMode: true })
|
||||||
|
|
||||||
|
describe('AssetFile', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<AssetFile file={file} ddo={ddo} />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('button to be disabled when not connected', () => {
|
||||||
|
const { container } = render(<AssetFile file={file} ddo={ddo} />)
|
||||||
|
expect(container.querySelector('button')).toHaveAttribute('disabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('button to be enabled when connected', async () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<User.Provider value={contextConnectedMock}>
|
||||||
|
<AssetFile file={file} ddo={ddo} />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
const button = getByText('Get file')
|
||||||
|
expect(button).not.toHaveAttribute('disabled')
|
||||||
|
|
||||||
|
fireEvent.click(button)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders loading state', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<StateMock state={{ isLoading: true }}>
|
||||||
|
<AssetFile file={file} ddo={ddo} />
|
||||||
|
</StateMock>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.spinner')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders error', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<StateMock state={{ error: 'Hello Error' }}>
|
||||||
|
<AssetFile file={file} ddo={ddo} />
|
||||||
|
</StateMock>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.error')).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('.error')).toHaveTextContent(
|
||||||
|
'Hello Error'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import { Logger } from '@oceanprotocol/squid'
|
import { Logger, DDO, File } from '@oceanprotocol/squid'
|
||||||
import filesize from 'filesize'
|
import filesize from 'filesize'
|
||||||
import Button from '../../components/atoms/Button'
|
import Button from '../../components/atoms/Button'
|
||||||
import Spinner from '../../components/atoms/Spinner'
|
import Spinner from '../../components/atoms/Spinner'
|
||||||
@ -8,8 +8,8 @@ import styles from './AssetFile.module.scss'
|
|||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
|
|
||||||
interface AssetFileProps {
|
interface AssetFileProps {
|
||||||
file: any
|
file: File
|
||||||
ddo: any
|
ddo: DDO
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AssetFileState {
|
interface AssetFileState {
|
||||||
@ -30,7 +30,7 @@ export default class AssetFile extends PureComponent<
|
|||||||
|
|
||||||
private resetState = () => this.setState({ isLoading: true, error: '' })
|
private resetState = () => this.setState({ isLoading: true, error: '' })
|
||||||
|
|
||||||
private purchaseAsset = async (ddo: any, index: number) => {
|
private purchaseAsset = async (ddo: DDO, index: number) => {
|
||||||
this.resetState()
|
this.resetState()
|
||||||
|
|
||||||
ReactGA.event({
|
ReactGA.event({
|
||||||
@ -76,11 +76,12 @@ export default class AssetFile extends PureComponent<
|
|||||||
public render() {
|
public render() {
|
||||||
const { ddo, file } = this.props
|
const { ddo, file } = this.props
|
||||||
const { isLoading, message, error } = this.state
|
const { isLoading, message, error } = this.state
|
||||||
const { isLogged, isNile } = this.context
|
const { isLogged, isOceanNetwork } = this.context
|
||||||
|
const { index } = file
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.fileWrap}>
|
<div className={styles.fileWrap}>
|
||||||
<ul key={file.index} className={styles.file}>
|
<ul key={index} className={styles.file}>
|
||||||
<li>
|
<li>
|
||||||
{file.contentType && file.contentType.split('/')[1]}
|
{file.contentType && file.contentType.split('/')[1]}
|
||||||
</li>
|
</li>
|
||||||
@ -97,8 +98,11 @@ export default class AssetFile extends PureComponent<
|
|||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
className={styles.buttonMain}
|
className={styles.buttonMain}
|
||||||
onClick={() => this.purchaseAsset(ddo, file.index)}
|
// TODO: remove the || 0 once hack
|
||||||
disabled={!isLogged || !isNile}
|
// https://github.com/oceanprotocol/squid-js/pull/221
|
||||||
|
// is released
|
||||||
|
onClick={() => this.purchaseAsset(ddo, index || 0)}
|
||||||
|
disabled={!isLogged || !isOceanNetwork}
|
||||||
>
|
>
|
||||||
Get file
|
Get file
|
||||||
</Button>
|
</Button>
|
||||||
|
43
client/src/routes/Details/AssetFilesDetails.test.tsx
Normal file
43
client/src/routes/Details/AssetFilesDetails.test.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import { DDO } from '@oceanprotocol/squid'
|
||||||
|
import AssetFilesDetails from './AssetFilesDetails'
|
||||||
|
import { User } from '../../context'
|
||||||
|
import { userMockConnected } from '../../../__mocks__/user-mock'
|
||||||
|
|
||||||
|
describe('AssetFilesDetails', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const files = [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
url: 'https://hello.com'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<AssetFilesDetails files={files} ddo={({} as any) as DDO} />
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders nothing when no files', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<AssetFilesDetails files={[]} ddo={({} as any) as DDO} />
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toHaveTextContent('No files attached.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides Web3message when all connected', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider value={userMockConnected}>
|
||||||
|
<AssetFilesDetails
|
||||||
|
files={[{ index: 0, url: '' }]}
|
||||||
|
ddo={({} as any) as DDO}
|
||||||
|
/>
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.status')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,31 +1,32 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
|
import { DDO, File } from '@oceanprotocol/squid'
|
||||||
import AssetFile from './AssetFile'
|
import AssetFile from './AssetFile'
|
||||||
import { User } from '../../context'
|
import { User } from '../../context'
|
||||||
import Web3message from '../../components/organisms/Web3message'
|
import Web3message from '../../components/organisms/Web3message'
|
||||||
import styles from './AssetFilesDetails.module.scss'
|
import styles from './AssetFilesDetails.module.scss'
|
||||||
|
|
||||||
export default class AssetFilesDetails extends PureComponent<{
|
export default class AssetFilesDetails extends PureComponent<{
|
||||||
files: any[]
|
files: File[]
|
||||||
ddo: any
|
ddo: DDO
|
||||||
}> {
|
}> {
|
||||||
public render() {
|
public render() {
|
||||||
const { files, ddo } = this.props
|
const { files, ddo } = this.props
|
||||||
|
|
||||||
return files ? (
|
return files.length ? (
|
||||||
<>
|
<>
|
||||||
<div className={styles.files}>
|
<div className={styles.files}>
|
||||||
{files.map(file => (
|
{files.map(file => (
|
||||||
<AssetFile key={file.index} ddo={ddo} file={file} />
|
<AssetFile key={file.index} ddo={ddo} file={file} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<User.Consumer>
|
{(!this.context.isOceanNetwork || !this.context.isLogged) && (
|
||||||
{states =>
|
<Web3message />
|
||||||
(!states.isNile || !states.isLogged) && <Web3message />
|
)}
|
||||||
}
|
|
||||||
</User.Consumer>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div>No files attached.</div>
|
<div>No files attached.</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssetFilesDetails.contextType = User
|
||||||
|
21
client/src/routes/Details/index.test.tsx
Normal file
21
client/src/routes/Details/index.test.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import Details from './index'
|
||||||
|
|
||||||
|
describe('Details', () => {
|
||||||
|
it('renders loading state by default', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Details
|
||||||
|
location={{
|
||||||
|
search: '',
|
||||||
|
pathname: '/',
|
||||||
|
state: '',
|
||||||
|
hash: ''
|
||||||
|
}}
|
||||||
|
match={{ params: { did: '' } }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('.loader')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,4 +1,5 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
|
import { DDO, MetaData, Logger } from '@oceanprotocol/squid'
|
||||||
import Route from '../../components/templates/Route'
|
import Route from '../../components/templates/Route'
|
||||||
import Spinner from '../../components/atoms/Spinner'
|
import Spinner from '../../components/atoms/Spinner'
|
||||||
import { User } from '../../context'
|
import { User } from '../../context'
|
||||||
@ -7,23 +8,37 @@ import stylesApp from '../../App.module.scss'
|
|||||||
|
|
||||||
interface DetailsProps {
|
interface DetailsProps {
|
||||||
location: Location
|
location: Location
|
||||||
match: any
|
match: {
|
||||||
|
params: {
|
||||||
|
did: string
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DetailsState {
|
interface DetailsState {
|
||||||
ddo: any
|
ddo: DDO
|
||||||
metadata: { base: { name: string } }
|
metadata: MetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Details extends Component<DetailsProps, DetailsState> {
|
export default class Details extends Component<DetailsProps, DetailsState> {
|
||||||
public state = { ddo: {}, metadata: { base: { name: '' } } }
|
public state = {
|
||||||
|
ddo: ({} as any) as DDO,
|
||||||
|
metadata: ({ base: { name: '' } } as any) as MetaData
|
||||||
|
}
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
const ddo = await this.context.ocean.assets.resolve(
|
this.getData()
|
||||||
this.props.match.params.did
|
}
|
||||||
)
|
|
||||||
const { metadata } = ddo.findServiceByType('Metadata')
|
private async getData() {
|
||||||
this.setState({ ddo, metadata: { base: metadata.base } })
|
try {
|
||||||
|
const { ocean } = this.context
|
||||||
|
const ddo = await ocean.assets.resolve(this.props.match.params.did)
|
||||||
|
const { metadata } = ddo.findServiceByType('Metadata')
|
||||||
|
this.setState({ ddo, metadata })
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(error.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
43
client/src/routes/Faucet.test.tsx
Normal file
43
client/src/routes/Faucet.test.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from 'react-testing-library'
|
||||||
|
import Faucet from './Faucet'
|
||||||
|
import { User } from '../context'
|
||||||
|
import { userMockConnected } from '../../__mocks__/user-mock'
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
const utils = render(
|
||||||
|
<User.Provider value={userMockConnected}>
|
||||||
|
<Faucet />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
const button = utils.getByText('Request Ether')
|
||||||
|
const { container } = utils
|
||||||
|
return {
|
||||||
|
button,
|
||||||
|
container,
|
||||||
|
...utils
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Faucet', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<Faucet />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows actions when connected', () => {
|
||||||
|
const { button } = setup()
|
||||||
|
|
||||||
|
expect(button).toBeInTheDocument()
|
||||||
|
expect(button).not.toHaveAttribute('disabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fires requestFromFaucet', () => {
|
||||||
|
const { button, getByText } = setup()
|
||||||
|
|
||||||
|
fireEvent.click(button)
|
||||||
|
expect(userMockConnected.requestFromFaucet).toHaveBeenCalledTimes(1)
|
||||||
|
// check for spinner
|
||||||
|
expect(getByText('Getting Ether...')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -46,7 +46,7 @@ export default class Faucet extends PureComponent<{}, FaucetState> {
|
|||||||
trxHash
|
trxHash
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({ isLoading: false, error })
|
this.setState({ isLoading: false, error: error.message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,9 @@ export default class Faucet extends PureComponent<{}, FaucetState> {
|
|||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
onClick={() => this.getTokens(this.context.requestFromFaucet)}
|
onClick={() => this.getTokens(this.context.requestFromFaucet)}
|
||||||
disabled={!this.context.isLogged || !this.context.isNile}
|
disabled={
|
||||||
|
!this.context.isLogged || !this.context.isOceanNetwork
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Request Ether
|
Request Ether
|
||||||
</Button>
|
</Button>
|
||||||
|
38
client/src/routes/History.test.tsx
Normal file
38
client/src/routes/History.test.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import { User } from '../context'
|
||||||
|
import History from './History'
|
||||||
|
|
||||||
|
describe('History', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<History />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('outputs Web3 message when no Web3 detected', () => {
|
||||||
|
const context = {
|
||||||
|
isLogged: false,
|
||||||
|
isLoading: false,
|
||||||
|
isWeb3: false,
|
||||||
|
isOceanNetwork: false,
|
||||||
|
account: '',
|
||||||
|
web3: {},
|
||||||
|
ocean: {},
|
||||||
|
balance: { eth: 0, ocn: 0 },
|
||||||
|
network: '',
|
||||||
|
requestFromFaucet: () => {},
|
||||||
|
unlockAccounts: () => {},
|
||||||
|
message: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider value={context}>
|
||||||
|
<History />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.message')).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('.message')).toHaveTextContent(
|
||||||
|
'Not a Web3 Browser.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -8,7 +8,7 @@ export default class History extends Component {
|
|||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<Route title="History">
|
<Route title="History">
|
||||||
{(!this.context.isLogged || !this.context.isNile) && (
|
{(!this.context.isLogged || !this.context.isOceanNetwork) && (
|
||||||
<Web3message />
|
<Web3message />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
10
client/src/routes/Home.test.tsx
Normal file
10
client/src/routes/Home.test.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import Home from './Home'
|
||||||
|
|
||||||
|
describe('Home', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<Home history={''} />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -7,9 +7,10 @@ import AssetsUser from '../components/organisms/AssetsUser'
|
|||||||
import styles from './Home.module.scss'
|
import styles from './Home.module.scss'
|
||||||
|
|
||||||
import meta from '../data/meta.json'
|
import meta from '../data/meta.json'
|
||||||
|
import { History } from 'history'
|
||||||
|
|
||||||
interface HomeProps {
|
interface HomeProps {
|
||||||
history: any
|
history: History
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HomeState {
|
interface HomeState {
|
||||||
@ -54,9 +55,7 @@ class Home extends Component<HomeProps, HomeState> {
|
|||||||
|
|
||||||
private searchAssets = (event: FormEvent<HTMLFormElement>) => {
|
private searchAssets = (event: FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.props.history.push(
|
this.props.history.push(`/search?text=${this.state.search}`)
|
||||||
`/search?text=${JSON.stringify(this.state.search)}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
client/src/routes/NotFound.test.tsx
Normal file
10
client/src/routes/NotFound.test.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import NotFound from './NotFound'
|
||||||
|
|
||||||
|
describe('NotFound', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<NotFound />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
39
client/src/routes/Publish/Files/Item.test.tsx
Normal file
39
client/src/routes/Publish/Files/Item.test.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import Item from './Item'
|
||||||
|
|
||||||
|
describe('Item', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const item = {
|
||||||
|
url: 'https://hello.com/hello.zip',
|
||||||
|
found: true,
|
||||||
|
contentType: 'application/zip',
|
||||||
|
contentLength: 10
|
||||||
|
}
|
||||||
|
const { container } = render(
|
||||||
|
<Item item={item} removeItem={() => null} />
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns unknown strings', () => {
|
||||||
|
const item = {
|
||||||
|
url: 'https://hello.com/hello.zip',
|
||||||
|
found: false,
|
||||||
|
contentType: '',
|
||||||
|
contentLength: 10
|
||||||
|
}
|
||||||
|
const { container } = render(
|
||||||
|
<Item item={item} removeItem={() => null} />
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.details')).toHaveTextContent(
|
||||||
|
'unknown type'
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.details')).toHaveTextContent(
|
||||||
|
'unknown size'
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.details')).toHaveTextContent(
|
||||||
|
'not confirmed'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
63
client/src/routes/Publish/Files/ItemForm.test.tsx
Normal file
63
client/src/routes/Publish/Files/ItemForm.test.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from 'react-testing-library'
|
||||||
|
import ItemForm from './ItemForm'
|
||||||
|
|
||||||
|
const addItem = jest.fn()
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
const utils = render(<ItemForm placeholder={'Hello'} addItem={addItem} />)
|
||||||
|
const input = utils.getByPlaceholderText('Hello')
|
||||||
|
const button = utils.getByText('Add File')
|
||||||
|
const { container } = utils
|
||||||
|
return {
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
container,
|
||||||
|
...utils
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ItemForm', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = setup()
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fires addItem', async () => {
|
||||||
|
const { input, button } = setup()
|
||||||
|
|
||||||
|
fireEvent.change(input, {
|
||||||
|
target: { value: 'https://hello.com' }
|
||||||
|
})
|
||||||
|
fireEvent.click(button)
|
||||||
|
expect(addItem).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not fire addItem when no url present', () => {
|
||||||
|
const { input, button, container } = setup()
|
||||||
|
|
||||||
|
// empty url
|
||||||
|
fireEvent.change(input, {
|
||||||
|
target: { value: '' }
|
||||||
|
})
|
||||||
|
fireEvent.click(button)
|
||||||
|
expect(container.querySelector('.error')).toHaveTextContent(
|
||||||
|
'Please fill in all required fields.'
|
||||||
|
)
|
||||||
|
|
||||||
|
// invalid url
|
||||||
|
fireEvent.change(input, {
|
||||||
|
target: { value: 'blabla' }
|
||||||
|
})
|
||||||
|
fireEvent.click(button)
|
||||||
|
expect(container.querySelector('.error')).toHaveTextContent(
|
||||||
|
'Please enter a valid URL.'
|
||||||
|
)
|
||||||
|
|
||||||
|
// clear out errors
|
||||||
|
fireEvent.change(input, {
|
||||||
|
target: { value: 'blablabla' }
|
||||||
|
})
|
||||||
|
expect(container.querySelector('.error')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -25,7 +25,7 @@ export default class ItemForm extends PureComponent<
|
|||||||
noUrl: false
|
noUrl: false
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleSubmit = (e: Event) => {
|
private handleSubmit = (e: Event) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const { url } = this.state
|
const { url } = this.state
|
||||||
@ -45,14 +45,14 @@ export default class ItemForm extends PureComponent<
|
|||||||
this.props.addItem(url)
|
this.props.addItem(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChangeUrl = (e: React.FormEvent<HTMLInputElement>) => {
|
private onChangeUrl = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
this.setState({ url: e.currentTarget.value })
|
this.setState({ url: e.currentTarget.value })
|
||||||
this.clearErrors()
|
this.clearErrors()
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearErrors() {
|
private clearErrors() {
|
||||||
if (this.state.hasError) this.setState({ hasError: false })
|
if (this.state.hasError) this.setState({ hasError: false })
|
||||||
if (this.state.noUrl) this.setState({ noUrl: true })
|
if (this.state.noUrl) this.setState({ noUrl: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
90
client/src/routes/Publish/Files/index.test.tsx
Normal file
90
client/src/routes/Publish/Files/index.test.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent, waitForElement } from 'react-testing-library'
|
||||||
|
import Files, { getFileCompression } from '.'
|
||||||
|
|
||||||
|
const onChange = jest.fn()
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
{
|
||||||
|
found: true,
|
||||||
|
url: 'https://hello.com',
|
||||||
|
checksum: 'cccccc',
|
||||||
|
checksumType: 'MD5',
|
||||||
|
contentLength: 100,
|
||||||
|
contentType: 'application/zip',
|
||||||
|
resourceId: 'xxx',
|
||||||
|
encoding: 'UTF-8',
|
||||||
|
compression: 'zip'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
const utils = render(
|
||||||
|
<Files
|
||||||
|
files={files}
|
||||||
|
placeholder={'Hello'}
|
||||||
|
name={'Hello'}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
const { container } = utils
|
||||||
|
return { container, ...utils }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Files', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = setup()
|
||||||
|
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('.itemForm')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('new file form can be opened and closed', async () => {
|
||||||
|
const { container, getByText } = setup()
|
||||||
|
|
||||||
|
// open
|
||||||
|
fireEvent.click(getByText('+ Add a file'))
|
||||||
|
await waitForElement(() => getByText('- Cancel'))
|
||||||
|
expect(container.querySelector('.itemForm')).toBeInTheDocument()
|
||||||
|
|
||||||
|
// close
|
||||||
|
fireEvent.click(getByText('- Cancel'))
|
||||||
|
await waitForElement(() => getByText('+ Add a file'))
|
||||||
|
expect(container.querySelector('.grow-exit')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('item can be removed', async () => {
|
||||||
|
const { getByTitle } = setup()
|
||||||
|
|
||||||
|
fireEvent.click(getByTitle('Remove item'))
|
||||||
|
expect(files.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('item can be added', async () => {
|
||||||
|
const { getByText, getByPlaceholderText } = setup()
|
||||||
|
|
||||||
|
fireEvent.click(getByText('+ Add a file'))
|
||||||
|
await waitForElement(() => getByText('- Cancel'))
|
||||||
|
fireEvent.change(getByPlaceholderText('Hello'), {
|
||||||
|
target: { value: 'https://hello.com' }
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText('Add File'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getFileCompression', () => {
|
||||||
|
it('outputs known compression', async () => {
|
||||||
|
const compression = await getFileCompression('application/zip')
|
||||||
|
expect(compression).toBe('zip')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('outputs known x- compression', async () => {
|
||||||
|
const compression = await getFileCompression('application/x-gtar')
|
||||||
|
expect(compression).toBe('gtar')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('outputs unknown compression', async () => {
|
||||||
|
const compression = await getFileCompression('blabla')
|
||||||
|
expect(compression).toBe('none')
|
||||||
|
})
|
||||||
|
})
|
@ -38,7 +38,7 @@ interface FilesStates {
|
|||||||
isFormShown: boolean
|
isFormShown: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFileCompression = async (contentType: string) => {
|
export const getFileCompression = async (contentType: string) => {
|
||||||
// TODO: add all the possible archive & compression MIME types
|
// TODO: add all the possible archive & compression MIME types
|
||||||
if (
|
if (
|
||||||
contentType === 'application/zip' ||
|
contentType === 'application/zip' ||
|
||||||
@ -69,14 +69,21 @@ export default class Files extends PureComponent<FilesProps, FilesStates> {
|
|||||||
isFormShown: false
|
isFormShown: false
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleForm = (e: Event) => {
|
private toggleForm = (e: Event) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
this.setState({ isFormShown: !this.state.isFormShown })
|
this.setState({ isFormShown: !this.state.isFormShown })
|
||||||
}
|
}
|
||||||
|
|
||||||
public addItem = async (value: string) => {
|
private addItem = async (value: string) => {
|
||||||
let res: any
|
let res: {
|
||||||
|
result: {
|
||||||
|
contentLength: number
|
||||||
|
contentType: string
|
||||||
|
found: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let file: File = {
|
let file: File = {
|
||||||
url: value,
|
url: value,
|
||||||
found: false,
|
found: false,
|
||||||
@ -104,6 +111,7 @@ export default class Files extends PureComponent<FilesProps, FilesStates> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// error
|
// error
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.files.push(file)
|
this.props.files.push(file)
|
||||||
const event = {
|
const event = {
|
||||||
currentTarget: {
|
currentTarget: {
|
||||||
@ -115,7 +123,7 @@ export default class Files extends PureComponent<FilesProps, FilesStates> {
|
|||||||
this.setState({ isFormShown: !this.state.isFormShown })
|
this.setState({ isFormShown: !this.state.isFormShown })
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeItem = (index: number) => {
|
private removeItem = (index: number) => {
|
||||||
this.props.files.splice(index, 1)
|
this.props.files.splice(index, 1)
|
||||||
const event = {
|
const event = {
|
||||||
currentTarget: {
|
currentTarget: {
|
||||||
|
19
client/src/routes/Publish/Progress.test.tsx
Normal file
19
client/src/routes/Publish/Progress.test.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import Progress from './Progress'
|
||||||
|
|
||||||
|
describe('Progress', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Progress currentStep={1} steps={[{ title: '' }]} />
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders completed state', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Progress currentStep={2} steps={[{ title: '' }]} />
|
||||||
|
)
|
||||||
|
expect(container.querySelector('li')).toHaveClass('completed')
|
||||||
|
})
|
||||||
|
})
|
46
client/src/routes/Publish/Step.test.tsx
Normal file
46
client/src/routes/Publish/Step.test.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import Step from './Step'
|
||||||
|
|
||||||
|
const stateMock = {
|
||||||
|
validationStatus: {
|
||||||
|
1: { allFieldsValid: true },
|
||||||
|
2: { allFieldsValid: true },
|
||||||
|
3: { allFieldsValid: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsMock = {
|
||||||
|
inputChange: () => null,
|
||||||
|
inputToArrayChange: () => null,
|
||||||
|
state: stateMock,
|
||||||
|
title: 'Hello',
|
||||||
|
description: 'description',
|
||||||
|
next: () => null,
|
||||||
|
prev: () => null,
|
||||||
|
tryAgain: () => null,
|
||||||
|
toStart: () => null
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Step', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Step currentStep={1} index={0} totalSteps={3} {...propsMock} />
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders previous button one page bigger than 1', () => {
|
||||||
|
const { queryByText } = render(
|
||||||
|
<Step currentStep={2} index={1} totalSteps={3} {...propsMock} />
|
||||||
|
)
|
||||||
|
expect(queryByText('← Previous')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not render next button when on last step', () => {
|
||||||
|
const { queryByText } = render(
|
||||||
|
<Step currentStep={3} index={2} totalSteps={3} {...propsMock} />
|
||||||
|
)
|
||||||
|
expect(queryByText('Next →')).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
76
client/src/routes/Publish/StepRegisterContent.test.tsx
Normal file
76
client/src/routes/Publish/StepRegisterContent.test.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from 'react-testing-library'
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom'
|
||||||
|
import StepRegisterContent from './StepRegisterContent'
|
||||||
|
|
||||||
|
const stateMock = {
|
||||||
|
publishedDid: '',
|
||||||
|
publishingError: '',
|
||||||
|
isPublishing: false,
|
||||||
|
isPublished: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsMock = {
|
||||||
|
tryAgain: jest.fn(),
|
||||||
|
toStart: jest.fn(),
|
||||||
|
content: 'Hello'
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('StepRegisterContent', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Router>
|
||||||
|
<StepRegisterContent state={stateMock} {...propsMock} />
|
||||||
|
</Router>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders publishing state', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Router>
|
||||||
|
<StepRegisterContent
|
||||||
|
state={{ ...stateMock, isPublishing: true }}
|
||||||
|
{...propsMock}
|
||||||
|
/>
|
||||||
|
</Router>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.spinnerMessage')).toHaveTextContent(
|
||||||
|
'Please sign with your crypto wallet'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders published state', () => {
|
||||||
|
const { container, getByText } = render(
|
||||||
|
<Router>
|
||||||
|
<StepRegisterContent
|
||||||
|
state={{ ...stateMock, isPublished: true }}
|
||||||
|
{...propsMock}
|
||||||
|
/>
|
||||||
|
</Router>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('.success')).toHaveTextContent(
|
||||||
|
'Your asset is published!'
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(getByText('Publish another asset'))
|
||||||
|
expect(propsMock.toStart).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders error state', () => {
|
||||||
|
const { container, getByText } = render(
|
||||||
|
<Router>
|
||||||
|
<StepRegisterContent
|
||||||
|
state={{ ...stateMock, publishingError: 'Error!' }}
|
||||||
|
{...propsMock}
|
||||||
|
/>
|
||||||
|
</Router>
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
container.querySelector('.message:last-child')
|
||||||
|
).toHaveTextContent('Something went wrong')
|
||||||
|
|
||||||
|
fireEvent.click(getByText('try again'))
|
||||||
|
expect(propsMock.tryAgain).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
@ -7,7 +7,12 @@ import styles from './StepRegisterContent.module.scss'
|
|||||||
interface StepRegisterContentProps {
|
interface StepRegisterContentProps {
|
||||||
tryAgain(): void
|
tryAgain(): void
|
||||||
toStart(): void
|
toStart(): void
|
||||||
state: any
|
state: {
|
||||||
|
publishedDid: string
|
||||||
|
isPublishing: boolean
|
||||||
|
publishingError: string
|
||||||
|
isPublished: boolean
|
||||||
|
}
|
||||||
content?: string
|
content?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
client/src/routes/Publish/index.test.tsx
Normal file
11
client/src/routes/Publish/index.test.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import Publish from '.'
|
||||||
|
|
||||||
|
describe('Progress', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container, getByText } = render(<Publish />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
expect(getByText('Next →')).toHaveAttribute('disabled')
|
||||||
|
})
|
||||||
|
})
|
@ -319,7 +319,7 @@ class Publish extends Component<{}, PublishState> {
|
|||||||
title="Publish"
|
title="Publish"
|
||||||
description="Publish a new data set into the Ocean Protocol Network."
|
description="Publish a new data set into the Ocean Protocol Network."
|
||||||
>
|
>
|
||||||
{(!this.context.isLogged || !this.context.isNile) && (
|
{(!this.context.isLogged || !this.context.isOceanNetwork) && (
|
||||||
<Web3message />
|
<Web3message />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
51
client/src/routes/Search.test.tsx
Normal file
51
client/src/routes/Search.test.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import Search from './Search'
|
||||||
|
import { User } from '../context'
|
||||||
|
import { createMemoryHistory } from 'history'
|
||||||
|
|
||||||
|
describe('Search', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider
|
||||||
|
value={{
|
||||||
|
isLogged: false,
|
||||||
|
isLoading: false,
|
||||||
|
isWeb3: false,
|
||||||
|
isOceanNetwork: false,
|
||||||
|
account: '',
|
||||||
|
web3: {},
|
||||||
|
ocean: {
|
||||||
|
aquarius: {
|
||||||
|
queryMetadata: () => {
|
||||||
|
return {
|
||||||
|
results: [],
|
||||||
|
totalResults: 1,
|
||||||
|
totalPages: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
balance: { eth: 0, ocn: 0 },
|
||||||
|
network: '',
|
||||||
|
requestFromFaucet: () => {},
|
||||||
|
unlockAccounts: () => {},
|
||||||
|
message: ''
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Search
|
||||||
|
location={{
|
||||||
|
search: '?text=Hello&page=1',
|
||||||
|
pathname: '/search',
|
||||||
|
state: '',
|
||||||
|
hash: ''
|
||||||
|
}}
|
||||||
|
history={history}
|
||||||
|
/>
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,5 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import queryString from 'query-string'
|
import queryString from 'query-string'
|
||||||
|
import { History, Location } from 'history'
|
||||||
import { Logger } from '@oceanprotocol/squid'
|
import { Logger } from '@oceanprotocol/squid'
|
||||||
import Spinner from '../components/atoms/Spinner'
|
import Spinner from '../components/atoms/Spinner'
|
||||||
import Route from '../components/templates/Route'
|
import Route from '../components/templates/Route'
|
||||||
@ -10,7 +11,7 @@ import styles from './Search.module.scss'
|
|||||||
|
|
||||||
interface SearchProps {
|
interface SearchProps {
|
||||||
location: Location
|
location: Location
|
||||||
history: any
|
history: History
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SearchState {
|
interface SearchState {
|
||||||
@ -40,7 +41,7 @@ export default class Search extends PureComponent<SearchProps, SearchState> {
|
|||||||
const searchPage = queryString.parse(this.props.location.search).page
|
const searchPage = queryString.parse(this.props.location.search).page
|
||||||
|
|
||||||
await this.setState({
|
await this.setState({
|
||||||
searchTerm: JSON.stringify(searchTerm)
|
searchTerm: encodeURIComponent(`${searchTerm}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
// switch to respective page if query string is present
|
// switch to respective page if query string is present
|
||||||
@ -53,11 +54,13 @@ export default class Search extends PureComponent<SearchProps, SearchState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private searchAssets = async () => {
|
private searchAssets = async () => {
|
||||||
|
const { ocean } = this.context
|
||||||
|
|
||||||
const searchQuery = {
|
const searchQuery = {
|
||||||
offset: this.state.offset,
|
offset: this.state.offset,
|
||||||
page: this.state.currentPage,
|
page: this.state.currentPage,
|
||||||
query: {
|
query: {
|
||||||
text: [this.state.searchTerm],
|
text: [decodeURIComponent(this.state.searchTerm)],
|
||||||
price: [-1, 1]
|
price: [-1, 1]
|
||||||
},
|
},
|
||||||
sort: {
|
sort: {
|
||||||
@ -65,16 +68,18 @@ export default class Search extends PureComponent<SearchProps, SearchState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const search = await this.context.ocean.aquarius.queryMetadata(
|
try {
|
||||||
searchQuery
|
const search = await ocean.aquarius.queryMetadata(searchQuery)
|
||||||
)
|
this.setState({
|
||||||
this.setState({
|
results: search.results,
|
||||||
results: search.results,
|
totalResults: search.totalResults,
|
||||||
totalResults: search.totalResults,
|
totalPages: search.totalPages,
|
||||||
totalPages: search.totalPages,
|
isLoading: false
|
||||||
isLoading: false
|
})
|
||||||
})
|
} catch (error) {
|
||||||
Logger.log(`Loaded ${this.state.results.length} assets`)
|
Logger.error(error)
|
||||||
|
this.setState({ isLoading: false })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handlePageClick = async (data: { selected: number }) => {
|
private handlePageClick = async (data: { selected: number }) => {
|
||||||
@ -112,9 +117,9 @@ export default class Search extends PureComponent<SearchProps, SearchState> {
|
|||||||
<h2
|
<h2
|
||||||
className={styles.resultsTitle}
|
className={styles.resultsTitle}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `${totalResults} results for <span>${
|
__html: `${totalResults} results for <span>${decodeURIComponent(
|
||||||
this.state.searchTerm
|
this.state.searchTerm
|
||||||
}</span>`
|
)}</span>`
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
10
client/src/routes/Styleguide.test.tsx
Normal file
10
client/src/routes/Styleguide.test.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import Styleguide from './Styleguide'
|
||||||
|
|
||||||
|
describe('Styleguide', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<Styleguide />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -19,7 +19,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "squid-js",
|
"name": "squid-js",
|
||||||
"version": "~0.5.4"
|
"version": "~0.5.10"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "faucet",
|
"name": "faucet",
|
||||||
|
1722
package-lock.json
generated
1722
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "commons",
|
"name": "commons",
|
||||||
"description": "Ocean Protocol marketplace to explore, download, and publish open data sets.",
|
"description": "Ocean Protocol marketplace to explore, download, and publish open data sets.",
|
||||||
"version": "0.2.5",
|
"version": "0.2.11",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "./scripts/install.sh",
|
"install": "./scripts/install.sh",
|
||||||
@ -31,16 +31,16 @@
|
|||||||
"concurrently": "^4.1.0",
|
"concurrently": "^4.1.0",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-config-oceanprotocol": "^1.3.0",
|
"eslint-config-oceanprotocol": "^1.3.0",
|
||||||
"eslint-config-prettier": "^4.1.0",
|
"eslint-config-prettier": "^4.2.0",
|
||||||
"eslint-plugin-prettier": "^3.0.1",
|
"eslint-plugin-prettier": "^3.0.1",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.17.0",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"release-it": "^10.4.3",
|
"release-it": "^11.0.2",
|
||||||
"stylelint": "^10.0.1",
|
"stylelint": "^10.0.1",
|
||||||
"stylelint-config-bigchaindb": "^1.2.1",
|
"stylelint-config-bigchaindb": "^1.2.2",
|
||||||
"stylelint-config-css-modules": "^1.3.0",
|
"stylelint-config-css-modules": "^1.4.0",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^18.3.0",
|
||||||
"typescript": "^3.4.4"
|
"typescript": "^3.4.5"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
12
scripts/coverage.sh
Executable file
12
scripts/coverage.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#/usr/bin/env/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
components="server client"
|
||||||
|
|
||||||
|
for component in $components
|
||||||
|
do
|
||||||
|
printf "\n\nReporting coverage: $component\n"
|
||||||
|
cd $component
|
||||||
|
npm run coverage
|
||||||
|
cd ..
|
||||||
|
done
|
@ -5,7 +5,7 @@ components="server client"
|
|||||||
|
|
||||||
for component in $components
|
for component in $components
|
||||||
do
|
do
|
||||||
echo "\n\nInstalling dependencies: $component\n"
|
printf "\n\nInstalling dependencies: $component\n"
|
||||||
cd $component
|
cd $component
|
||||||
npm install
|
npm install
|
||||||
cd ..
|
cd ..
|
||||||
|
2865
server/package-lock.json
generated
2865
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,27 +9,23 @@
|
|||||||
"serve": "node dist/server.js",
|
"serve": "node dist/server.js",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "jest --coverage",
|
"test": "jest --coverage",
|
||||||
"test:watch": "jest --coverage --watch"
|
"test:watch": "jest --coverage --watch",
|
||||||
|
"coverage": "cat coverage/lcov.info | codacy-coverage --token 8801f827fe1144ffa85cd7da94f2bbf7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oceanprotocol/squid": "^0.5.5",
|
"@oceanprotocol/squid": "^0.5.5",
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
"color-js": "^1.0.5",
|
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-validator": "^5.3.1",
|
"express-validator": "^5.3.1",
|
||||||
"lusca": "^1.6.1",
|
|
||||||
"morgan": "^1.9.1",
|
"morgan": "^1.9.1",
|
||||||
"multer": "^1.4.1",
|
"request": "^2.88.0"
|
||||||
"request": "^2.88.0",
|
|
||||||
"uuid": "^3.3.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/body-parser": "^1.17.0",
|
"@types/body-parser": "^1.17.0",
|
||||||
"@types/compression": "0.0.36",
|
"@types/compression": "0.0.36",
|
||||||
"@types/debug": "^4.1.4",
|
"@types/debug": "^4.1.4",
|
||||||
"@types/dotenv": "^6.1.1",
|
|
||||||
"@types/express": "^4.16.1",
|
"@types/express": "^4.16.1",
|
||||||
"@types/jasmine": "^3.3.12",
|
"@types/jasmine": "^3.3.12",
|
||||||
"@types/jest": "^24.0.11",
|
"@types/jest": "^24.0.11",
|
||||||
@ -42,7 +38,7 @@
|
|||||||
"supertest": "^4.0.2",
|
"supertest": "^4.0.2",
|
||||||
"ts-jest": "^24.0.2",
|
"ts-jest": "^24.0.2",
|
||||||
"ts-node": "^8.1.0",
|
"ts-node": "^8.1.0",
|
||||||
"typescript": "^3.4.3"
|
"typescript": "^3.4.5"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express'
|
import { Router, Request, Response } from 'express'
|
||||||
import request from 'request'
|
import request from 'request'
|
||||||
|
|
||||||
export class UrlCheckRouter {
|
export class UrlCheckRouter {
|
||||||
|
@ -50,6 +50,7 @@ app.use(morgan('dev'))
|
|||||||
app.use(bodyParser.json())
|
app.use(bodyParser.json())
|
||||||
app.use(bodyParser.urlencoded({ extended: false }))
|
app.use(bodyParser.urlencoded({ extended: false }))
|
||||||
app.use(compression())
|
app.use(compression())
|
||||||
|
|
||||||
// routes
|
// routes
|
||||||
app.use('/api/v1/urlcheck', UrlCheckRouter)
|
app.use('/api/v1/urlcheck', UrlCheckRouter)
|
||||||
app.use('/api/v1/ddo', DdoRouter)
|
app.use('/api/v1/ddo', DdoRouter)
|
||||||
|
@ -7,9 +7,21 @@ afterAll(done => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('POST /api/v1/urlcheck', () => {
|
describe('POST /api/v1/urlcheck', () => {
|
||||||
it('responds with json', function(done) {
|
it('responds with json', async () => {
|
||||||
request(server)
|
const response = await request(server).post('/api/v1/urlcheck')
|
||||||
.post('/api/v1/urlcheck')
|
expect(response.statusCode).toBe(200)
|
||||||
.expect(200, done)
|
})
|
||||||
|
|
||||||
|
it('responds with error message when url is missing', async () => {
|
||||||
|
const response = await request(server).post('/api/v1/urlcheck')
|
||||||
|
const text = await JSON.parse(response.text)
|
||||||
|
expect(text.message).toBe('missing url')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Errors', () => {
|
||||||
|
it('responds with 404 on unknown path', async () => {
|
||||||
|
const response = await request(server).post('/whatever')
|
||||||
|
expect(response.statusCode).toBe(404)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user