1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Tests for @shared components (#1737)

* asset teaser test, story, useUserPreferences Jest mock

* fixtures & mocks

* more tests

* cleanup

* more tests

* more tests

* more tests

* more tests

* typing fix

* package updates

* reorg

* more tests

* more tests

* more tests

* more tests

* reorg, more tests

* reorg fixes

* more tests

* rebase fix

* subgraph query typing fix

* restore some storybook stories

* more tests

* more tests

* package lock fix

* graphql override
This commit is contained in:
Matthias Kretschmann 2022-11-04 13:52:40 +00:00 committed by GitHub
parent a042387804
commit 56f5e77c49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
131 changed files with 4013 additions and 7390 deletions

View File

@ -0,0 +1,71 @@
import { Asset } from '@oceanprotocol/lib'
export const assetAquarius: Asset = {
'@context': ['https://w3id.org/did/v1'],
id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630',
nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
version: '4.1.0',
chainId: 5,
metadata: {
created: '2022-09-29T11:30:26Z',
updated: '2022-09-29T11:30:26Z',
type: 'dataset',
name: 'Testitest',
description: 'This is a test.',
tags: [],
author: 'Test User',
license: 'https://market.oceanprotocol.com/terms',
additionalInformation: {
termsAndConditions: true
}
},
services: [
{
id: 'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1',
type: 'access',
files:
'0x04022ef1afafe340f41b261ef721b8dd55dee094975cc70330803d760beef38871948ce572ff1c533d56cda2665749ed2eb8283e243ec5ee19011f510b6b263b2da0af537e3f1fdff7ddd90fa26c7a4761a6d26928bc1348a302634012aac7998e92c84456ab73e9a847120c44ebda15781787e8c382391b2eaefc8b8d36998f3998d1c4647f4f7bb28f4278093c1d231f66e78f81452049443b9e540aeb42ebbdc1b748c024eb10218532814736e241efa1c2a687685b4e2ea7a877685aa0ea325d1a8cf765d1b423b32d81ec3c3e22fc9c15c6b9b71f2862edaec4e4cf7c3a638ffc0ecb88ede3cabb511d4780543a53c001a95f42de1877796e13c997b57bc671507e92198934b4ea7c2e6554993388421253e8c2f10458dec872a7ebfa71b6e77ed359222c93261ba252028c5da06ccf8defcd529885b2125816325a47e23728b513',
datatokenAddress: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
serviceEndpoint: 'https://v4.provider.goerli.oceanprotocol.com',
timeout: 604800
}
],
event: {
tx: '0x3e07a75c1cc5d4146222a93ab4319144e60ecca3ebfb8b15f1ff339d6f479dc9',
block: 7680195,
from: '0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7',
contract: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
datetime: '2022-09-29T11:31:12'
},
nft: {
address: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
name: 'Ocean Data NFT',
symbol: 'OCEAN-NFT',
state: 0,
tokenURI:
'data:application/json;base64,eyJuYW1lIjoiT2NlYW4gRGF0YSBORlQiLCJzeW1ib2wiOiJPQ0VBTi1ORlQiLCJkZXNjcmlwdGlvbiI6IlRoaXMgTkZUIHJlcHJlc2VudHMgYW4gYXNzZXQgaW4gdGhlIE9jZWFuIFByb3RvY29sIHY0IGVjb3N5c3RlbS5cblxuVmlldyBvbiBPY2VhbiBNYXJrZXQ6IGh0dHBzOi8vbWFya2V0Lm9jZWFucHJvdG9jb2wuY29tL2Fzc2V0L2RpZDpvcDo2NjU0YjA3OTM3NjViMjY5Njk2Y2VjOGQyZjBkMDc3ZDliYmNkZDNjNGYwMzNkOTQxYWI5Njg0ZThhZDA2NjMwIiwiZXh0ZXJuYWxfdXJsIjoiaHR0cHM6Ly9tYXJrZXQub2NlYW5wcm90b2NvbC5jb20vYXNzZXQvZGlkOm9wOjY2NTRiMDc5Mzc2NWIyNjk2OTZjZWM4ZDJmMGQwNzdkOWJiY2RkM2M0ZjAzM2Q5NDFhYjk2ODRlOGFkMDY2MzAiLCJiYWNrZ3JvdW5kX2NvbG9yIjoiMTQxNDE0IiwiaW1hZ2VfZGF0YSI6ImRhdGE6aW1hZ2Uvc3ZnK3htbCwlM0Nzdmcgdmlld0JveD0nMCAwIDk5IDk5JyBmaWxsPSd1bmRlZmluZWQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyclM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5Mjc3JyBkPSdNMCw5OUwwLDI5QzksMjUgMTksMjIgMjksMjFDMzgsMTkgNDksMTkgNjEsMjFDNzIsMjIgODUsMjUgOTksMjlMOTksOTlaJy8lM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5MmJiJyBkPSdNMCw5OUwwLDU1QzgsNDkgMTcsNDQgMjgsNDNDMzgsNDEgNTAsNDIgNjMsNDNDNzUsNDMgODcsNDIgOTksNDJMOTksOTlaJyUzRSUzQy9wYXRoJTNFJTNDcGF0aCBmaWxsPSclMjNmZjQwOTJmZicgZD0nTTAsOTlMMCw2OEMxMSw2NiAyMiw2NSAzMiw2N0M0MSw2OCA1MCw3MyA2MSw3NkM3MSw3OCA4NSw3OCA5OSw3OUw5OSw5OVonJTNFJTNDL3BhdGglM0UlM0Mvc3ZnJTNFIn0=',
owner: '0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0',
created: '2022-09-29T11:31:12'
},
datatokens: [
{
address: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
name: 'Stupendous Orca Token',
symbol: 'STUORC-59',
serviceId:
'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1'
}
],
stats: {
orders: 22
// price: {
// value: 3231343254,
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
// tokenSymbol: 'OCEAN'
// }
},
purgatory: {
state: false,
reason: ''
}
}

View File

@ -0,0 +1,26 @@
import { assetAquarius } from './assetAquarius'
export const asset: AssetExtended = {
...assetAquarius,
accessDetails: {
publisherMarketOrderFee: '0',
type: 'fixed',
addressOrId:
'0x00e3b740e4d8bf6e97010ecb5b14d1b7efc0913bfa291fcf5adb8eb9e6c29e93',
price: '3231343254',
isPurchasable: true,
isOwned: false,
validOrderTx: null,
baseToken: {
address: '0xcfdda22c9837ae76e0faa845354f33c62e03653a',
name: 'Ocean Token',
symbol: 'OCEAN',
decimals: 18
},
datatoken: {
address: '0x067e1e6ec580f3f0f6781679a4a5ab07a6464b08',
name: 'Stupendous Orca Token',
symbol: 'STUORC-59'
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,64 @@
export default {
prices: {
h2o: {
btc: 0.00009013,
cad: 2.38,
cny: 12.36,
eth: 0.00132713,
eur: 1.78,
gbp: 1.55,
hkd: 13.53,
inr: 141.78,
jpy: 253.21,
link: 0.24050954,
rub: 109.81,
sgd: 2.47,
usd: 1.72
},
'matic-network': {
btc: 0.0000414,
cad: 1.093,
cny: 5.67,
eth: 0.00061102,
eur: 0.816959,
gbp: 0.715112,
hkd: 6.21,
inr: 65.06,
jpy: 116.23,
link: 0.11072143,
rub: 50.4,
sgd: 1.14,
usd: 0.790922
},
ethereum: {
btc: 0.06775775,
cad: 1789.03,
cny: 9288.09,
eth: 1,
eur: 1337.1,
gbp: 1170.41,
hkd: 10161.71,
inr: 106490,
jpy: 190239,
link: 181.216,
rub: 82491,
sgd: 1859.58,
usd: 1294.49
},
'ocean-protocol': {
btc: 0.00000809,
cad: 0.213554,
cny: 1.11,
eth: 0.00011937,
eur: 0.159608,
gbp: 0.13971,
hkd: 1.21,
inr: 12.71,
jpy: 22.71,
link: 0.02163146,
rub: 9.85,
sgd: 0.221976,
usd: 0.154521
}
}
}

View File

@ -0,0 +1,41 @@
export const columns = [
{
name: 'Name',
selector: (row: any) => row.name,
maxWidth: '45rem',
grow: 1
},
{
name: 'Symbol',
selector: (row: any) => row.symbol,
maxWidth: '10rem'
},
{
name: 'Price',
selector: (row: any) => row.price,
right: true
}
]
export const data = [
{
name: 'Title asset',
symbol: 'DATA-70',
price: '1.011'
},
{
name: 'Title asset Title asset Title asset Title asset Title asset',
symbol: 'DATA-71',
price: '1.011'
},
{
name: 'Title asset',
symbol: 'DATA-72',
price: '1.011'
},
{
name: 'Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset',
symbol: 'DATA-71',
price: '1.011'
}
]

View File

@ -0,0 +1,10 @@
export default {
debug: true,
currency: 'EUR',
locale: 'en-US',
chainIds: [5, 1, 137, 56, 1285, 246],
bookmarks: [],
privacyPolicySlug: '/privacy/en',
showPPC: true,
infiniteApproval: false
}

View File

@ -0,0 +1,33 @@
export default {
accountEns: 'jellymcjellyfish.eth',
accountEnsAvatar:
'https://metadata.ens.domains/mainnet/avatar/jellymcjellyfish.eth',
accountId: '0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0',
approvedBaseTokens: [
{
address: '0xcfdda22c9837ae76e0faa845354f33c62e03653a',
symbol: 'OCEAN',
name: 'Ocean Token',
decimals: 18
}
],
balance: { eth: '0', ocean: '1000' },
block: 7751969,
chainId: 5,
connect: jest.fn(),
isSupportedOceanNetwork: true,
isTestnet: true,
logout: jest.fn(),
networkData: { name: 'Görli', title: 'Ethereum Testnet Görli', chain: 'ETH' },
networkDisplayName: 'ETH Görli',
networkId: 5,
web3: { currentProvider: {} },
web3Loading: false,
web3Modal: { show: false, eventController: {}, connect: jest.fn() },
web3Provider: {},
web3ProviderInfo: {
id: 'injected',
name: 'MetaMask',
logo: ''
}
}

View File

@ -0,0 +1,3 @@
import { assets } from '../../__fixtures__/assetsWithAccessDetails'
export const getAccessDetailsForAssets = jest.fn().mockResolvedValue(assets)

View File

@ -0,0 +1 @@
export default true

View File

@ -1,7 +1,23 @@
import '@testing-library/jest-dom/extend-expect' import '@testing-library/jest-dom/extend-expect'
import './__mocks__/matchMedia' import './__mocks__/matchMedia'
import marketMetadataMock from './__mocks__/MarketMetadata'
import marketMetadata from './__fixtures__/marketMetadata'
import userPreferences from './__fixtures__/userPreferences'
import web3 from './__fixtures__/web3'
import { asset } from './__fixtures__/assetWithAccessDetails'
jest.mock('../../src/@context/MarketMetadata', () => ({ jest.mock('../../src/@context/MarketMetadata', () => ({
useMarketMetadata: () => marketMetadataMock useMarketMetadata: () => marketMetadata
}))
jest.mock('../../src/@context/UserPreferences', () => ({
useUserPreferences: () => userPreferences
}))
jest.mock('../../src/@context/Web3', () => ({
useWeb3: () => web3
}))
jest.mock('../../../@context/Asset', () => ({
useAsset: () => ({ asset })
})) }))

7605
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@
"format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write", "format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write",
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
"deploy:s3": "bash scripts/deploy-s3.sh", "deploy:s3": "bash scripts/deploy-s3.sh",
"postinstall": "husky install && rm -r node_modules/apollo-language-server/node_modules/graphql", "postinstall": "husky install",
"codegen:apollo": "apollo client:codegen --endpoint=https://v4.subgraph.goerli.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/", "codegen:apollo": "apollo client:codegen --endpoint=https://v4.subgraph.goerli.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/",
"storybook": "cross-env NODE_ENV=test start-storybook -p 6006 --quiet", "storybook": "cross-env NODE_ENV=test start-storybook -p 6006 --quiet",
"storybook:build": "cross-env NODE_ENV=test build-storybook" "storybook:build": "cross-env NODE_ENV=test build-storybook"
@ -43,8 +43,6 @@
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"is-url-superb": "^6.1.0", "is-url-superb": "^6.1.0",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash.debounce": "^4.0.8",
"lodash.omit": "^4.5.0",
"match-sorter": "^6.3.1", "match-sorter": "^6.3.1",
"myetherwallet-blockies": "^0.1.1", "myetherwallet-blockies": "^0.1.1",
"next": "12.3.1", "next": "12.3.1",
@ -72,8 +70,8 @@
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-essentials": "^6.5.12", "@storybook/addon-essentials": "^6.5.13",
"@storybook/builder-webpack5": "^6.5.12", "@storybook/builder-webpack5": "^6.5.13",
"@storybook/manager-webpack5": "^6.5.13", "@storybook/manager-webpack5": "^6.5.13",
"@storybook/react": "^6.5.13", "@storybook/react": "^6.5.13",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
@ -81,26 +79,24 @@
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@types/js-cookie": "^3.0.2", "@types/js-cookie": "^3.0.2",
"@types/loadable__component": "^5.13.4", "@types/loadable__component": "^5.13.4",
"@types/lodash.debounce": "^4.0.7", "@types/node": "^18.8.5",
"@types/lodash.omit": "^4.5.7",
"@types/node": "^18.7.18",
"@types/react": "^18.0.21", "@types/react": "^18.0.21",
"@types/react-dom": "^18.0.5", "@types/react-dom": "^18.0.5",
"@types/react-modal": "^3.13.1", "@types/react-modal": "^3.13.1",
"@types/react-paginate": "^7.1.1", "@types/react-paginate": "^7.1.1",
"@types/remove-markdown": "^0.3.1", "@types/remove-markdown": "^0.3.1",
"@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.38.1", "@typescript-eslint/parser": "^5.42.0",
"apollo": "^2.34.0", "apollo": "^2.34.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.23.1", "eslint": "^8.25.0",
"eslint-config-oceanprotocol": "^2.0.4", "eslint-config-oceanprotocol": "^2.0.4",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest-dom": "^4.0.2", "eslint-plugin-jest-dom": "^4.0.2",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.8", "eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-testing-library": "^5.7.0", "eslint-plugin-testing-library": "^5.7.2",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"husky": "^8.0.1", "husky": "^8.0.1",
"jest": "^29.1.2", "jest": "^29.1.2",
@ -111,14 +107,17 @@
"serve": "^14.0.1", "serve": "^14.0.1",
"stream-http": "^3.2.0", "stream-http": "^3.2.0",
"tsconfig-paths-webpack-plugin": "^4.0.0", "tsconfig-paths-webpack-plugin": "^4.0.0",
"typescript": "^4.8.3" "typescript": "^4.8.4"
},
"overrides": {
"graphql": "15.8.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/oceanprotocol/market" "url": "https://github.com/oceanprotocol/market"
}, },
"engines": { "engines": {
"node": ">=14" "node": "16"
}, },
"browserslist": [ "browserslist": [
">0.2%", ">0.2%",

View File

@ -323,7 +323,7 @@ export async function getAccessDetailsForAssets(
}, },
queryContext queryContext
) )
tokenQueryResult.data?.tokens.forEach((token) => { tokenQueryResult?.data?.tokens?.forEach((token) => {
const currentAsset = assetsExtended.find( const currentAsset = assetsExtended.find(
(asset) => (asset) =>
asset.services[0].datatokenAddress.toLowerCase() === token.id asset.services[0].datatokenAddress.toLowerCase() === token.id

View File

@ -1,5 +1,5 @@
import { Asset, LoggerInstance } from '@oceanprotocol/lib' import { Asset, LoggerInstance } from '@oceanprotocol/lib'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
import axios, { CancelToken, AxiosResponse } from 'axios' import axios, { CancelToken, AxiosResponse } from 'axios'
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData' import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
import { metadataCacheUri } from '../../app.config' import { metadataCacheUri } from '../../app.config'

View File

@ -1,6 +1,6 @@
import { getAccessDetailsForAssets } from './accessDetailsAndPricing' import { getAccessDetailsForAssets } from './accessDetailsAndPricing'
import { PublisherTrustedAlgorithm, Asset } from '@oceanprotocol/lib' import { PublisherTrustedAlgorithm, Asset } from '@oceanprotocol/lib'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
import { getServiceByName } from './ddo' import { getServiceByName } from './ddo'
export async function transformAssetToAssetSelection( export async function transformAssetToAssetSelection(

View File

@ -21,10 +21,10 @@ import {
} from './aquarius' } from './aquarius'
import { fetchDataForMultipleChains } from './subgraph' import { fetchDataForMultipleChains } from './subgraph'
import { getServiceById, getServiceByName } from './ddo' import { getServiceById, getServiceByName } from './ddo'
import { SortTermOptions } from 'src/@types/aquarius/SearchQuery' import { SortTermOptions } from '../@types/aquarius/SearchQuery'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
import { transformAssetToAssetSelection } from './assetConvertor' import { transformAssetToAssetSelection } from './assetConvertor'
import { ComputeEditForm } from 'src/components/Asset/Edit/_types' import { ComputeEditForm } from '../components/Asset/Edit/_types'
import { getFileDidInfo } from './provider' import { getFileDidInfo } from './provider'
const getComputeOrders = gql` const getComputeOrders = gql`

View File

@ -13,7 +13,7 @@ import {
} from '@hooks/useNetworkMetadata' } from '@hooks/useNetworkMetadata'
import { getAssetsFromNftList } from './aquarius' import { getAssetsFromNftList } from './aquarius'
import { chainIdsSupported } from 'app.config' import { chainIdsSupported } from 'app.config'
import { Asset, LoggerInstance } from '@oceanprotocol/lib' import { Asset } from '@oceanprotocol/lib'
const AllLocked = gql` const AllLocked = gql`
query AllLocked { query AllLocked {
@ -42,7 +42,7 @@ const NftOwnAllocation = gql`
} }
` `
const OceanLocked = gql` const OceanLocked = gql`
query OceanLocked($address: String) { query OceanLocked($address: ID!) {
veOCEAN(id: $address) { veOCEAN(id: $address) {
id id
lockedAmount lockedAmount

View File

@ -1,24 +0,0 @@
.accountList {
display: grid;
grid-template-columns: 1fr;
gap: calc(var(--spacer) / 2);
}
@media screen and (min-width: 25rem) {
.accountList {
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
gap: var(--spacer);
}
}
.empty {
color: var(--color-secondary);
font-size: var(--font-size-small);
font-style: italic;
}
.loaderWrap {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -0,0 +1,29 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import AddToken, { AddTokenProps } from '@shared/AddToken'
export default {
title: 'Component/@shared/AddToken',
component: AddToken
} as ComponentMeta<typeof AddToken>
const Template: ComponentStory<typeof AddToken> = (args: AddTokenProps) => {
return <AddToken {...args} />
}
interface Props {
args: AddTokenProps
}
export const Default: Props = Template.bind({})
Default.args = {
address: '0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8',
symbol: 'OCEAN'
}
export const Minimal: Props = Template.bind({})
Minimal.args = {
address: '0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8',
symbol: 'OCEAN',
minimal: true
}

View File

@ -0,0 +1,24 @@
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import testRender from '../../../../.jest/testRender'
import AddToken from './index'
jest.mock('../../../@utils/web3', () => ({ addTokenToWallet: jest.fn() }))
describe('@shared/AddToken', () => {
const propsBase = {
address: '0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8',
symbol: 'OCEAN'
}
testRender(<AddToken {...propsBase} />)
it('renders with custom text', () => {
render(<AddToken {...propsBase} text="Hello Text" />)
expect(screen.getByText('Hello Text')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button'))
})
it('renders minimal', () => {
render(<AddToken {...propsBase} minimal />)
})
})

View File

@ -8,19 +8,21 @@ import styles from './index.module.css'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
export interface AddTokenProps {
address: string
symbol: string
text?: string
className?: string
minimal?: boolean
}
export default function AddToken({ export default function AddToken({
address, address,
symbol, symbol,
text, text,
className, className,
minimal minimal
}: { }: AddTokenProps): ReactElement {
address: string
symbol: string
text?: string
className?: string
minimal?: boolean
}): ReactElement {
const { web3Provider } = useWeb3() const { web3Provider } = useWeb3()
const styleClasses = cx({ const styleClasses = cx({

View File

@ -0,0 +1,65 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import AddAnnouncementBanner, {
AnnouncementBannerProps
} from '@shared/AnnouncementBanner'
export default {
title: 'Component/@shared/AnnouncementBanner',
component: AddAnnouncementBanner
} as ComponentMeta<typeof AddAnnouncementBanner>
const Template: ComponentStory<typeof AddAnnouncementBanner> = (
args: AnnouncementBannerProps
) => <AddAnnouncementBanner {...args} />
interface Props {
args: AnnouncementBannerProps
}
export const Default: Props = Template.bind({})
Default.args = {
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce malesuada ipsum ac enim auctor placerat.',
action: {
name: 'see more',
handleAction: () => {
alert('Link clicked!')
}
}
}
export const Success: Props = Template.bind({})
Success.args = {
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce malesuada ipsum ac enim auctor placerat.',
state: 'success',
action: {
name: 'see more',
handleAction: () => {
alert('Link clicked!')
}
}
}
export const Warning: Props = Template.bind({})
Warning.args = {
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce malesuada ipsum ac enim auctor placerat.',
state: 'warning',
action: {
name: 'see more',
handleAction: () => {
alert('Link clicked!')
}
}
}
export const Error: Props = Template.bind({})
Error.args = {
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce malesuada ipsum ac enim auctor placerat.',
state: 'error',
action: {
name: 'see more',
handleAction: () => {
alert('Link clicked!')
}
}
}

View File

@ -0,0 +1,13 @@
import React from 'react'
import testRender from '../../../../.jest/testRender'
import AnnouncementBanner from './index'
describe('@shared/AnnouncementBanner', () => {
testRender(
<AnnouncementBanner
text="# Hello World!"
action={{ name: 'hello', handleAction: jest.fn() }}
state="success"
/>
)
})

View File

@ -12,17 +12,19 @@ export interface AnnouncementAction {
handleAction: () => void handleAction: () => void
} }
export interface AnnouncementBannerProps {
text: string
action?: AnnouncementAction
state?: 'success' | 'warning' | 'error'
className?: string
}
export default function AnnouncementBanner({ export default function AnnouncementBanner({
text, text,
action, action,
state, state,
className className
}: { }: AnnouncementBannerProps): ReactElement {
text: string
action?: AnnouncementAction
state?: 'success' | 'warning' | 'error'
className?: string
}): ReactElement {
const styleClasses = cx({ const styleClasses = cx({
banner: true, banner: true,
error: state === 'error', error: state === 'error',

View File

@ -1,59 +0,0 @@
.display {
composes: selection from '@shared/FormFields/AssetSelection/index.module.css';
}
.display [class*='loaderWrap'] {
margin: calc(var(--spacer) / 3);
}
.scroll {
composes: scroll from '@shared/FormFields/AssetSelection/index.module.css';
margin-top: 0;
border-top: none;
width: 100%;
}
.row {
composes: row from '@shared/FormFields/AssetSelection/index.module.css';
}
.row:last-child {
border-bottom: none;
}
.row:first-child {
border-top: none;
}
.row:hover {
background-color: var(--background-content);
}
.info {
display: block;
width: 100%;
}
.title {
composes: title from '@shared/FormFields/AssetSelection/index.module.css';
}
.hover:hover {
color: var(--color-primary);
}
.price {
composes: price from '@shared/FormFields/AssetSelection/index.module.css';
}
.price [class*='symbol'] {
font-size: calc(var(--font-size-small) / 1.2) !important;
}
.did {
composes: did from '@shared/FormFields/AssetSelection/index.module.css';
}
.empty {
composes: empty from '@shared/FormFields/AssetSelection/index.module.css';
}

View File

@ -0,0 +1,35 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import MarketMetadataProvider from '@context/MarketMetadata'
import { UserPreferencesProvider } from '@context/UserPreferences'
import AssetList, { AssetListProps } from '.'
import { assets } from '../../../../.jest/__fixtures__/assetsWithAccessDetails'
export default {
title: 'Component/@shared/AssetList',
component: AssetList
} as ComponentMeta<typeof AssetList>
const Template: ComponentStory<typeof AssetList> = (args: AssetListProps) => {
return (
<MarketMetadataProvider>
<UserPreferencesProvider>
<AssetList {...args} />
</UserPreferencesProvider>
</MarketMetadataProvider>
)
}
export const Default: { args: AssetListProps } = Template.bind({})
Default.args = {
assets,
showPagination: true,
page: 1,
totalPages: 10
}
export const Empty: { args: AssetListProps } = Template.bind({})
Empty.args = {
assets: [],
showPagination: false
}

View File

@ -0,0 +1,32 @@
import { render, screen, fireEvent } from '@testing-library/react'
import React from 'react'
import AssetList from './index'
import { assetAquarius } from '../../../../.jest/__fixtures__/assetAquarius'
describe('@shared/AssetList', () => {
it('renders without crashing', async () => {
const onPageChange = jest.fn()
render(
<AssetList
assets={[assetAquarius]}
showPagination
page={1}
totalPages={10}
onPageChange={onPageChange}
/>
)
await screen.findAllByText('OCEAN')
fireEvent.click(screen.getByLabelText('Page 2'))
expect(onPageChange).toBeCalled()
})
it('renders empty', async () => {
render(<AssetList assets={[]} showPagination={false} isLoading={false} />)
await screen.findByText('No results found')
})
it('renders loading', async () => {
render(<AssetList assets={[]} showPagination={false} isLoading />)
})
})

View File

@ -2,15 +2,11 @@ import AssetTeaser from '@shared/AssetTeaser'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Pagination from '@shared/Pagination' import Pagination from '@shared/Pagination'
import styles from './index.module.css' import styles from './index.module.css'
import classNames from 'classnames/bind'
import Loader from '@shared/atoms/Loader' import Loader from '@shared/atoms/Loader'
import { useUserPreferences } from '@context/UserPreferences'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing' import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
const cx = classNames.bind(styles)
function LoaderArea() { function LoaderArea() {
return ( return (
<div className={styles.loaderWrap}> <div className={styles.loaderWrap}>
@ -19,7 +15,7 @@ function LoaderArea() {
) )
} }
declare type AssetListProps = { export declare type AssetListProps = {
assets: AssetExtended[] assets: AssetExtended[]
showPagination: boolean showPagination: boolean
page?: number page?: number
@ -40,14 +36,14 @@ export default function AssetList({
className, className,
noPublisher noPublisher
}: AssetListProps): ReactElement { }: AssetListProps): ReactElement {
const { chainIds } = useUserPreferences()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const [assetsWithPrices, setAssetsWithPrices] = useState<AssetExtended[]>() const [assetsWithPrices, setAssetsWithPrices] =
useState<AssetExtended[]>(assets)
const [loading, setLoading] = useState<boolean>(isLoading) const [loading, setLoading] = useState<boolean>(isLoading)
const isMounted = useIsMounted() const isMounted = useIsMounted()
useEffect(() => { useEffect(() => {
if (!assets) return if (!assets || !assets.length) return
setAssetsWithPrices(assets as AssetExtended[]) setAssetsWithPrices(assets as AssetExtended[])
setLoading(false) setLoading(false)
@ -67,16 +63,9 @@ export default function AssetList({
onPageChange(selected + 1) onPageChange(selected + 1)
} }
const styleClasses = cx({ const styleClasses = `${styles.assetList} ${className || ''}`
assetList: true,
[className]: className
})
return chainIds.length === 0 ? ( return assetsWithPrices && !loading ? (
<div className={styleClasses}>
<div className={styles.empty}>No network selected</div>
</div>
) : assetsWithPrices && !loading ? (
<> <>
<div className={styleClasses}> <div className={styleClasses}>
{assetsWithPrices.length > 0 ? ( {assetsWithPrices.length > 0 ? (

View File

@ -0,0 +1,24 @@
import React from 'react'
import testRender from '../../../../.jest/testRender'
import AssetListTitle from '.'
import { render } from '@testing-library/react'
jest.mock('../../../@utils/aquarius', () => ({
getAssetsNames: () => Promise.resolve('Test')
}))
describe('AssetListTitle', () => {
testRender(
<AssetListTitle asset={{ metadata: { name: 'Hello world' } } as any} />
)
it('renders with passed title', () => {
render(<AssetListTitle title="Hello Title" />)
})
it('renders with passed DID', () => {
render(
<AssetListTitle did="did:op:764b81877039fa2651b919fc91c399799acb837f270e6d17bfb7973fbe6e9408" />
)
})
})

View File

@ -1,7 +1,7 @@
import Link from 'next/link' import Link from 'next/link'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import { getAssetsNames } from '@utils/aquarius' import { getAssetsNames } from '@utils/aquarius'
import styles from './AssetListTitle.module.css' import styles from './index.module.css'
import axios from 'axios' import axios from 'axios'
import { Asset } from '@oceanprotocol/lib' import { Asset } from '@oceanprotocol/lib'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'

View File

@ -0,0 +1,8 @@
import React from 'react'
import testRender from '../../../../.jest/testRender'
import AssetTeaser from './index'
import { asset } from '../../../../.jest/__fixtures__/assetWithAccessDetails'
describe('@shared/AssetTeaser', () => {
testRender(<AssetTeaser asset={asset} />)
})

View File

@ -11,7 +11,7 @@ import { getServiceByName } from '@utils/ddo'
import { formatPrice } from '@shared/Price/PriceUnit' import { formatPrice } from '@shared/Price/PriceUnit'
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
declare type AssetTeaserProps = { export declare type AssetTeaserProps = {
asset: AssetExtended asset: AssetExtended
noPublisher?: boolean noPublisher?: boolean
} }

View File

@ -0,0 +1,7 @@
import React from 'react'
import testRender from '../../../../.jest/testRender'
import DebugOutput from './index'
describe('@shared/DebugOutput', () => {
testRender(<DebugOutput title="Debug" output="Hello Output" />)
})

View File

@ -0,0 +1,21 @@
import testRender from '../../../../.jest/testRender'
import { render, screen } from '@testing-library/react'
import React from 'react'
import ExplorerLink from './index'
describe('@shared/ExplorerLink', () => {
testRender(
<ExplorerLink networkId={1} path="/tx">
Hello Link
</ExplorerLink>
)
it('renders without networkId', () => {
render(
<ExplorerLink networkId={null} path="/tx">
Hello Link
</ExplorerLink>
)
expect(screen.getByRole('link')).toHaveTextContent('Hello Link')
})
})

View File

@ -1,12 +1,9 @@
import React, { ReactElement, ReactNode, useEffect, useState } from 'react' import React, { ReactElement, ReactNode, useEffect, useState } from 'react'
import External from '@images/external.svg' import External from '@images/external.svg'
import classNames from 'classnames/bind'
import { Config } from '@oceanprotocol/lib' import { Config } from '@oceanprotocol/lib'
import styles from './index.module.css' import styles from './index.module.css'
import { getOceanConfig } from '@utils/ocean' import { getOceanConfig } from '@utils/ocean'
const cx = classNames.bind(styles)
export default function ExplorerLink({ export default function ExplorerLink({
networkId, networkId,
path, path,
@ -20,10 +17,6 @@ export default function ExplorerLink({
}): ReactElement { }): ReactElement {
const [url, setUrl] = useState<string>() const [url, setUrl] = useState<string>()
const [oceanConfig, setOceanConfig] = useState<Config>() const [oceanConfig, setOceanConfig] = useState<Config>()
const styleClasses = cx({
link: true,
[className]: className
})
useEffect(() => { useEffect(() => {
if (!networkId) return if (!networkId) return
@ -39,7 +32,7 @@ export default function ExplorerLink({
title={`View on ${oceanConfig?.explorerUri}`} title={`View on ${oceanConfig?.explorerUri}`}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className={styleClasses} className={`${styles.link} ${className || ''}`}
> >
{children} <External /> {children} <External />
</a> </a>

View File

@ -0,0 +1,37 @@
import testRender from '../../../../.jest/testRender'
import { FileInfo } from '@oceanprotocol/lib'
import { render } from '@testing-library/react'
import React from 'react'
import FileIcon from './index'
describe('@shared/FileIcon', () => {
const file: FileInfo = {
type: 'url',
contentType: 'text/plain',
contentLength: '123'
}
testRender(<FileIcon file={file} />)
it('renders small', () => {
render(<FileIcon file={file} small />)
})
it('renders loading', () => {
render(<FileIcon file={file} isLoading />)
})
it('renders empty', () => {
const file: FileInfo = { type: 'url' }
render(<FileIcon file={file} />)
})
it('renders with 0 contentLength', () => {
const file: FileInfo = {
type: 'url',
contentType: 'text/plain',
contentLength: '0'
}
render(<FileIcon file={file} />)
})
})

View File

@ -1,17 +0,0 @@
export function prettySize(
bytes: number,
separator = ' ',
postFix = ''
): string {
if (bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
const i = Math.min(
Math.floor(Math.log(bytes) / Math.log(1024)),
sizes.length - 1
)
return `${(bytes / 1024 ** i).toFixed(i ? 1 : 0)}${separator}${
sizes[i]
}${postFix}`
}
return 'n/a'
}

View File

@ -0,0 +1,21 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import Error from './Error'
describe('@shared/FormInput/Error', () => {
const propsBase = {
value: '',
touched: false,
initialTouched: false
}
it('renders without crashing', () => {
render(<Error meta={{ ...propsBase, error: 'Hello Error' }} />)
expect(screen.getByText('Hello Error')).toBeInTheDocument()
})
it('renders nothing without error passed', () => {
render(<Error meta={{ ...propsBase }} />)
expect(screen.queryByText('Hello Error')).not.toBeInTheDocument()
})
})

View File

@ -1,9 +1,6 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './Help.module.css' import styles from './Help.module.css'
import Markdown from '@shared/Markdown' import Markdown from '@shared/Markdown'
import classNames from 'classnames/bind'
const cx = classNames.bind(styles)
const FormHelp = ({ const FormHelp = ({
children, children,
@ -12,12 +9,9 @@ const FormHelp = ({
children: string children: string
className?: string className?: string
}): ReactElement => { }): ReactElement => {
const styleClasses = cx({ return (
help: true, <Markdown className={`${styles.help} ${className || ''}`} text={children} />
[className]: className )
})
return <Markdown className={styleClasses} text={children} />
} }
export default FormHelp export default FormHelp

View File

@ -58,11 +58,11 @@
} }
.radio { .radio {
composes: radio from '@shared/FormInput/InputRadio.module.css'; composes: radio from '@shared/FormInput/InputElement/Radio/index.module.css';
} }
.checkbox { .checkbox {
composes: checkbox from '@shared/FormInput/InputRadio.module.css'; composes: checkbox from '@shared/FormInput/InputElement/Radio/index.module.css';
} }
.title { .title {

View File

@ -0,0 +1,52 @@
import AssetSelection, { AssetSelectionAsset } from './'
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
describe('@shared/FormInput/InputElement/AssetSelection', () => {
const assets: AssetSelectionAsset[] = [
{
did: 'did:op:xxx',
name: 'Asset',
price: '10',
checked: false,
symbol: 'OCEAN'
},
{
did: 'did:op:yyy',
name: 'Asset',
price: '10',
checked: true,
symbol: 'OCEAN'
},
{
did: 'did:op:zzz',
name: 'Asset',
price: '0',
checked: false,
symbol: 'OCEAN'
}
]
it('renders without crashing', () => {
render(<AssetSelection assets={assets} />)
const searchInput = screen.getByPlaceholderText(
'Search by title, datatoken, or DID...'
)
fireEvent.change(searchInput, { target: { value: 'Assets' } })
fireEvent.change(searchInput, { target: { value: '' } })
})
it('renders empty assetSelection', () => {
render(<AssetSelection assets={[]} />)
expect(screen.getByText('No assets found.')).toBeInTheDocument()
})
it('renders disabled assetSelection', () => {
render(<AssetSelection assets={[]} disabled />)
expect(screen.getByText('No assets found.')).toBeInTheDocument()
})
it('renders assetSelectionMultiple', () => {
render(<AssetSelection assets={assets} multiple />)
})
})

View File

@ -1,15 +1,12 @@
import React, { ChangeEvent, useState } from 'react' import React, { ChangeEvent, useState } from 'react'
import Dotdotdot from 'react-dotdotdot' import Dotdotdot from 'react-dotdotdot'
import slugify from 'slugify' import slugify from 'slugify'
import classNames from 'classnames/bind'
import PriceUnit from '@shared/Price/PriceUnit' import PriceUnit from '@shared/Price/PriceUnit'
import External from '@images/external.svg' import External from '@images/external.svg'
import InputElement from '@shared/FormInput/InputElement' import InputElement from '@shared/FormInput/InputElement'
import Loader from '@shared/atoms/Loader' import Loader from '@shared/atoms/Loader'
import styles from './index.module.css' import styles from './index.module.css'
const cx = classNames.bind(styles)
export interface AssetSelectionAsset { export interface AssetSelectionAsset {
did: string did: string
name: string name: string
@ -34,18 +31,19 @@ export default function AssetSelection({
}): JSX.Element { }): JSX.Element {
const [searchValue, setSearchValue] = useState('') const [searchValue, setSearchValue] = useState('')
const styleClassesInput = cx({ const styleClassesWrapper = `${styles.selection} ${
input: true, disabled ? styles.disabled : ''
[styles.checkbox]: multiple, }`
[styles.radio]: !multiple const styleClassesInput = `${styles.input} ${
}) multiple ? styles.checkbox : styles.radio
}`
function handleSearchInput(e: ChangeEvent<HTMLInputElement>) { function handleSearchInput(e: ChangeEvent<HTMLInputElement>) {
setSearchValue(e.target.value) setSearchValue(e.target.value)
} }
return ( return (
<div className={`${styles.selection} ${disabled ? styles.disabled : ''}`}> <div className={styleClassesWrapper}>
<InputElement <InputElement
type="search" type="search"
name="search" name="search"

View File

@ -0,0 +1,38 @@
import BoxSelection, { BoxSelectionOption } from './'
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
describe('@shared/FormInput/InputElement/BoxSelection', () => {
const handleChange = jest.fn()
const options: BoxSelectionOption[] = [
{
name: 'option1',
value: 'option1',
title: 'Option 1',
checked: true,
text: 'Option 1 Text',
icon: <div>Icon</div>
},
{
name: 'option2',
title: 'Option 2 Text',
checked: false
}
]
it('renders without crashing', () => {
render(
<BoxSelection name="box" options={options} handleChange={handleChange} />
)
fireEvent.click(screen.getByText('Option 2 Text'))
expect(handleChange).toHaveBeenCalled()
})
it('renders disabled', () => {
render(<BoxSelection name="box" options={options} disabled />)
})
it('renders loader without options', () => {
render(<BoxSelection name="box" options={null} />)
})
})

View File

@ -1,10 +1,7 @@
import React, { ChangeEvent } from 'react' import React, { ChangeEvent } from 'react'
import classNames from 'classnames/bind'
import Loader from '@shared/atoms/Loader' import Loader from '@shared/atoms/Loader'
import styles from './index.module.css' import styles from './index.module.css'
const cx = classNames.bind(styles)
export interface BoxSelectionOption { export interface BoxSelectionOption {
name: string name: string
value?: string value?: string
@ -26,15 +23,10 @@ export default function BoxSelection({
disabled?: boolean disabled?: boolean
handleChange?: (event: ChangeEvent<HTMLInputElement>) => void handleChange?: (event: ChangeEvent<HTMLInputElement>) => void
}): JSX.Element { }): JSX.Element {
const styleClassesWrapper = cx({ const styleClassesWrapper = `${styles.boxSelectionsWrapper} ${
boxSelectionsWrapper: true, disabled ? styles.disabled : ''
[styles.disabled]: disabled }`
}) const styleClassesInput = `${styles.input} ${styles.radio}`
const styleClassesInput = cx({
input: true,
radio: true
})
return ( return (
<div className={styleClassesWrapper}> <div className={styleClassesWrapper}>
@ -51,7 +43,7 @@ export default function BoxSelection({
type="radio" type="radio"
className={styleClassesInput} className={styleClassesInput}
disabled={disabled} disabled={disabled}
value={option.value ? option.value : option.name} value={option.value || option.name}
name={name} name={name}
/> />
<label <label

View File

@ -0,0 +1,59 @@
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import Datatoken from './index'
import { useField } from 'formik'
jest.mock('formik')
const props = {
name: 'Datatoken'
}
const mockMeta = {
touched: false,
error: '',
initialError: '',
initialTouched: false,
initialValue: '',
value: ''
}
const mockField = {
value: {
name: '',
symbol: ''
},
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'NFT'
}
const mockHelpers = {
setValue: jest.fn()
}
describe('@shared/FormInput/InputElement/Datatoken', () => {
it('renders without crashing', () => {
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
render(<Datatoken {...props} />)
fireEvent.click(screen.getByRole('button'))
})
it('does nothing when data already present', () => {
;(useField as jest.Mock).mockReturnValue([
{
value: {
name: 'Hello Name'
},
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'NFT'
},
mockMeta,
mockHelpers
])
render(<Datatoken {...props} />)
})
})

View File

@ -0,0 +1,125 @@
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import FilesInput from './index'
import { useField } from 'formik'
import { getFileUrlInfo } from '@utils/provider'
jest.mock('formik')
jest.mock('@utils/provider')
const props = {
name: 'File'
}
const mockMeta = {
touched: false,
error: '',
initialError: '',
initialTouched: false,
initialValue: '',
value: ''
}
const mockField = {
value: 'https://hello.com',
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'url'
}
const mockHelpers = {
setValue: jest.fn(),
setTouched: jest.fn()
}
const mockForm = {
values: {
services: [{ providerUrl: 'https://provider.url' }]
},
errors: {},
touched: {},
isSubmitting: false,
isValidating: false,
submitCount: 0,
setFieldError: jest.fn()
}
describe('@shared/FormInput/InputElement/FilesInput', () => {
it('renders without crashing', async () => {
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
;(getFileUrlInfo as jest.Mock).mockReturnValue([
{
valid: true,
url: 'https://hello.com',
contentType: 'text/html',
contentLength: 100
}
])
render(<FilesInput form={mockForm} {...props} />)
expect(screen.getByText('Validate')).toBeInTheDocument()
fireEvent.click(screen.getByText('Validate'))
// can't really re-mock our helpers.setValue() behavior switching
// to Info component, so we just wait for Validate button to be back again.
await screen.findByText('Validate')
expect(mockHelpers.setValue).toHaveBeenCalled()
})
it('renders fileinfo when file is valid', () => {
;(useField as jest.Mock).mockReturnValue([
{
value: [
{
valid: true,
url: 'https://hello.com',
contentType: 'text/html',
contentLength: 100
}
]
},
mockMeta,
mockHelpers
])
render(<FilesInput {...props} />)
expect(screen.getByText('https://hello.com')).toBeInTheDocument()
})
it('renders fileinfo without contentType', () => {
;(useField as jest.Mock).mockReturnValue([
{
value: [
{
valid: true,
url: 'https://hello.com',
contentLength: 100
}
]
},
mockMeta,
mockHelpers
])
render(<FilesInput {...props} />)
})
it('renders fileinfo placeholder when hideUrl is passed', () => {
;(useField as jest.Mock).mockReturnValue([
{
value: [
{
valid: true,
url: 'https://hello.com',
type: 'hidden'
}
]
},
mockMeta,
mockHelpers
])
render(<FilesInput {...props} />)
expect(
screen.getByText('https://oceanprotocol/placeholder')
).toBeInTheDocument()
})
})

View File

@ -1,17 +1,15 @@
import React, { ReactElement, useState } from 'react' import React, { ReactElement, useState } from 'react'
import { useField, useFormikContext } from 'formik' import { useField } from 'formik'
import FileInfo from './Info' import FileInfo from './Info'
import UrlInput from '../URLInput' import UrlInput from '../URLInput'
import { InputProps } from '@shared/FormInput' import { InputProps } from '@shared/FormInput'
import { getFileUrlInfo } from '@utils/provider' import { getFileUrlInfo } from '@utils/provider'
import { FormPublishData } from 'src/components/Publish/_types'
import { LoggerInstance } from '@oceanprotocol/lib' import { LoggerInstance } from '@oceanprotocol/lib'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
export default function FilesInput(props: InputProps): ReactElement { export default function FilesInput(props: InputProps): ReactElement {
const [field, meta, helpers] = useField(props.name) const [field, meta, helpers] = useField(props.name)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const { values, setFieldError } = useFormikContext<FormPublishData>()
const { asset } = useAsset() const { asset } = useAsset()
async function handleValidation(e: React.SyntheticEvent, url: string) { async function handleValidation(e: React.SyntheticEvent, url: string) {
@ -19,8 +17,8 @@ export default function FilesInput(props: InputProps): ReactElement {
e?.preventDefault() e?.preventDefault()
try { try {
const providerUrl = values?.services const providerUrl = props.form?.values?.services
? values?.services[0].providerUrl.url ? props.form?.values?.services[0].providerUrl.url
: asset.services[0].serviceEndpoint : asset.services[0].serviceEndpoint
setIsLoading(true) setIsLoading(true)
const checkedFile = await getFileUrlInfo(url, providerUrl) const checkedFile = await getFileUrlInfo(url, providerUrl)
@ -35,7 +33,7 @@ export default function FilesInput(props: InputProps): ReactElement {
// if all good, add file to formik state // if all good, add file to formik state
helpers.setValue([{ url, ...checkedFile[0] }]) helpers.setValue([{ url, ...checkedFile[0] }])
} catch (error) { } catch (error) {
setFieldError(`${field.name}[0].url`, error.message) props.form.setFieldError(`${field.name}[0].url`, error.message)
LoggerInstance.error(error.message) LoggerInstance.error(error.message)
} finally { } finally {
setIsLoading(false) setIsLoading(false)

View File

@ -0,0 +1,16 @@
export function prettySize(
bytes: number,
separator = ' ',
postFix = ''
): string {
if (!bytes) return 'n/a'
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
const i = Math.min(
Math.floor(Math.log(bytes) / Math.log(1024)),
sizes.length - 1
)
return `${(bytes / 1024 ** i).toFixed(i ? 1 : 0)}${separator}${
sizes[i]
}${postFix}`
}

View File

@ -0,0 +1,63 @@
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import Nft from './index'
import { useField } from 'formik'
jest.mock('formik')
const props = {
name: 'NFT'
}
const mockMeta = {
touched: false,
error: '',
initialError: '',
initialTouched: false,
initialValue: '',
value: ''
}
const mockField = {
value: {
name: '',
symbol: '',
description: '',
external_url: '',
background_color: '',
image_data: ''
},
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'NFT'
}
const mockHelpers = {
setValue: jest.fn()
}
describe('@shared/FormInput/InputElement/Nft', () => {
it('renders without crashing', () => {
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
render(<Nft {...props} />)
fireEvent.click(screen.getByRole('button'))
})
it('does nothing when data already present', () => {
;(useField as jest.Mock).mockReturnValue([
{
value: {
name: 'Hello Name'
},
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'NFT'
},
mockMeta,
mockHelpers
])
render(<Nft {...props} />)
})
})

View File

@ -16,7 +16,7 @@
.radio, .radio,
.checkbox { .checkbox {
composes: input from './InputElement.module.css'; composes: input from '../index.module.css';
position: relative; position: relative;
padding: 0; padding: 0;
width: 18px; width: 18px;

View File

@ -1,7 +1,7 @@
import React, { InputHTMLAttributes, ReactElement } from 'react' import React, { InputHTMLAttributes, ReactElement } from 'react'
import slugify from 'slugify' import slugify from 'slugify'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import styles from './InputRadio.module.css' import styles from './index.module.css'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)

View File

@ -2,11 +2,11 @@ import React, { ReactElement, useEffect, useState } from 'react'
import CreatableSelect from 'react-select/creatable' import CreatableSelect from 'react-select/creatable'
import { OnChangeValue } from 'react-select' import { OnChangeValue } from 'react-select'
import { useField } from 'formik' import { useField } from 'formik'
import { InputProps } from '.' import { InputProps } from '../..'
import { getTagsList } from '@utils/aquarius' import { getTagsList } from '@utils/aquarius'
import { chainIds } from 'app.config' import { chainIds } from '../../../../../../app.config'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import styles from './TagsAutoComplete.module.css' import styles from './index.module.css'
import { matchSorter } from 'match-sorter' import { matchSorter } from 'match-sorter'
interface AutoCompleteOption { interface AutoCompleteOption {

View File

@ -1,5 +1,5 @@
.input { .input {
composes: input from '@shared/FormInput/InputElement.module.css'; composes: input from '@shared/FormInput/InputElement/index.module.css';
} }
.hasError { .hasError {

View File

@ -0,0 +1,57 @@
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import URLInput, { URLInputProps } from './index'
import { useField } from 'formik'
jest.mock('formik')
const props: URLInputProps = {
submitText: 'Submit',
handleButtonClick: jest.fn(),
isLoading: false,
name: 'Hello Name'
}
const mockMeta = {
touched: false,
error: '',
initialError: '',
initialTouched: false,
initialValue: '',
value: ''
}
describe('@shared/FormInput/InputElement/URLInput', () => {
it('renders without crashing', () => {
const mockField = {
value: '',
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'url'
}
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta])
render(<URLInput {...props} />)
expect(screen.getByRole('button')).toBeDisabled()
fireEvent.change(screen.getByRole('textbox'), {
target: { value: 'https://google.com' }
})
})
it('renders button enabled with value', () => {
const mockField = {
value: 'https://google.com',
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'url'
}
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta])
render(<URLInput {...props} />)
expect(screen.getByRole('button')).toBeEnabled()
fireEvent.click(screen.getByRole('button'))
})
})

View File

@ -7,6 +7,14 @@ import InputGroup from '@shared/FormInput/InputGroup'
import InputElement from '@shared/FormInput/InputElement' import InputElement from '@shared/FormInput/InputElement'
import isUrl from 'is-url-superb' import isUrl from 'is-url-superb'
export interface URLInputProps {
submitText: string
handleButtonClick(e: React.SyntheticEvent, data: string): void
isLoading: boolean
name: string
checkUrl?: boolean
}
export default function URLInput({ export default function URLInput({
submitText, submitText,
handleButtonClick, handleButtonClick,
@ -14,13 +22,7 @@ export default function URLInput({
name, name,
checkUrl, checkUrl,
...props ...props
}: { }: URLInputProps): ReactElement {
submitText: string
handleButtonClick(e: React.SyntheticEvent, data: string): void
isLoading: boolean
name: string
checkUrl?: boolean
}): ReactElement {
const [field, meta] = useField(name) const [field, meta] = useField(name)
const [isButtonDisabled, setIsButtonDisabled] = useState(true) const [isButtonDisabled, setIsButtonDisabled] = useState(true)

View File

@ -1,17 +1,15 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './InputElement.module.css' import styles from './index.module.css'
import { InputProps } from '.' import { InputProps } from '..'
import FilesInput from '../FormFields/FilesInput' import FilesInput from './FilesInput'
import CustomProvider from '../FormFields/Provider' import CustomProvider from './Provider'
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection' import BoxSelection, { BoxSelectionOption } from './BoxSelection'
import Datatoken from '../FormFields/Datatoken' import Datatoken from './Datatoken'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import AssetSelection, { import AssetSelection, { AssetSelectionAsset } from './AssetSelection'
AssetSelectionAsset import Nft from './Nft'
} from '../FormFields/AssetSelection' import InputRadio from './Radio'
import Nft from '../FormFields/Nft' import ContainerInput from '@shared/FormInput/InputElement/ContainerInput'
import InputRadio from './InputRadio'
import ContainerInput from '@shared/FormFields/ContainerInput'
import TagsAutoComplete from './TagsAutoComplete' import TagsAutoComplete from './TagsAutoComplete'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
@ -87,12 +85,14 @@ export default function InputElement({
case 'radio': case 'radio':
case 'checkbox': case 'checkbox':
return <InputRadio options={options} inputSize={size} {...props} /> return (
<InputRadio options={options as string[]} inputSize={size} {...props} />
)
case 'assetSelection': case 'assetSelection':
return ( return (
<AssetSelection <AssetSelection
assets={options as unknown as AssetSelectionAsset[]} assets={options as AssetSelectionAsset[]}
{...field} {...field}
{...props} {...props}
/> />
@ -101,14 +101,14 @@ export default function InputElement({
case 'assetSelectionMultiple': case 'assetSelectionMultiple':
return ( return (
<AssetSelection <AssetSelection
assets={options as unknown as AssetSelectionAsset[]} assets={options as AssetSelectionAsset[]}
multiple multiple
{...field} {...field}
{...props} {...props}
/> />
) )
case 'files': case 'files':
return <FilesInput {...field} {...props} /> return <FilesInput {...field} form={form} {...props} />
case 'container': case 'container':
return <ContainerInput {...field} {...props} /> return <ContainerInput {...field} {...props} />
case 'providerUrl': case 'providerUrl':
@ -120,7 +120,7 @@ export default function InputElement({
case 'boxSelection': case 'boxSelection':
return ( return (
<BoxSelection <BoxSelection
options={options as unknown as BoxSelectionOption[]} options={options as BoxSelectionOption[]}
{...field} {...field}
{...props} {...props}
/> />

View File

@ -1,3 +0,0 @@
.row {
margin-bottom: var(--spacer);
}

View File

@ -1,8 +0,0 @@
import React, { ReactElement, ReactNode } from 'react'
import styles from './Row.module.css'
const Row = ({ children }: { children: ReactNode }): ReactElement => (
<div className={styles.row}>{children}</div>
)
export default Row

View File

@ -0,0 +1,105 @@
import { BoxSelectionOption } from '@shared/FormInput/InputElement/BoxSelection'
import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
import { render, screen } from '@testing-library/react'
import React from 'react'
import FormInput from './index'
describe('@shared/FormInput', () => {
it('renders without crashing', () => {
render(
<FormInput
type="text"
name="Hello Name"
label="Hello Label"
placeholder="Hello Placeholder"
required
help="Hello Help"
/>
)
expect(screen.getByText('Hello Label')).toBeInTheDocument()
expect(screen.getByPlaceholderText('Hello Placeholder')).toBeInTheDocument()
})
it('renders prominent help', () => {
render(<FormInput type="text" help="Hello Help" prominentHelp />)
expect(screen.getByText('Hello Help')).toBeInTheDocument()
})
it('renders disclaimer', () => {
render(<FormInput type="text" disclaimer="Hello Disclaimer" />)
expect(screen.getByText('Hello Disclaimer')).toBeInTheDocument()
})
it('renders with prefix & postfix', () => {
render(
<FormInput type="text" prefix="Hello Prefix" postfix="Hello Postfix" />
)
expect(screen.getByText('Hello Prefix')).toBeInTheDocument()
expect(screen.getByText('Hello Postfix')).toBeInTheDocument()
})
it('renders textarea', () => {
render(<FormInput type="textarea" />)
})
it('renders radio', () => {
render(<FormInput type="radio" options={['option1', 'option2']} />)
})
it('renders checkbox', () => {
render(<FormInput type="checkbox" options={['option1', 'option2']} />)
})
it('renders select', () => {
render(<FormInput type="select" options={['option1', 'option2']} />)
})
it('renders assetSelection', () => {
const assets: AssetSelectionAsset[] = [
{
did: 'did:op:xxx',
name: 'Asset',
price: '10',
checked: false,
symbol: 'OCEAN'
},
{
did: 'did:op:yyy',
name: 'Asset',
price: '10',
checked: true,
symbol: 'OCEAN'
}
]
render(<FormInput type="assetSelection" options={assets} />)
})
it('renders assetSelectionMultiple', () => {
render(<FormInput type="assetSelectionMultiple" />)
})
it('renders boxSelection', () => {
const options: BoxSelectionOption[] = [
{
name: 'option1',
title: 'Option 1',
checked: true,
text: 'Option 1 Text',
icon: <div>Icon</div>
},
{
name: 'option2',
title: 'Option 2',
checked: true
}
]
render(
<FormInput
type="boxSelection"
options={options}
onChange={() => jest.fn()}
/>
)
})
})

View File

@ -16,6 +16,8 @@ import Disclaimer from './Disclaimer'
import Tooltip from '@shared/atoms/Tooltip' import Tooltip from '@shared/atoms/Tooltip'
import Markdown from '@shared/Markdown' import Markdown from '@shared/Markdown'
import FormHelp from './Help' import FormHelp from './Help'
import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
import { BoxSelectionOption } from '@shared/FormInput/InputElement/BoxSelection'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
@ -28,7 +30,7 @@ export interface InputProps {
prominentHelp?: boolean prominentHelp?: boolean
tag?: string tag?: string
type?: string type?: string
options?: string[] options?: string[] | AssetSelectionAsset[] | BoxSelectionOption[]
sortOptions?: boolean sortOptions?: boolean
additionalComponent?: ReactElement additionalComponent?: ReactElement
value?: string | number value?: string | number

View File

@ -1,73 +0,0 @@
import * as React from 'react'
import { FormikProps, connect } from 'formik'
import debounce from 'lodash.debounce'
import omit from 'lodash.omit'
import isEqual from 'react-fast-compare'
import { LoggerInstance } from '@oceanprotocol/lib'
export interface PersistProps {
name: string
ignoreFields?: string[]
debounce?: number
isSessionStorage?: boolean
}
// TODO: refactor into functional component
class PersistImpl extends React.Component<
PersistProps & { formik: FormikProps<any> },
any
> {
static defaultProps = {
debounce: 300
}
saveForm = debounce((data: FormikProps<any>) => {
const dataToSave = this.omitIgnoredFields(data)
LoggerInstance.log('data to save', dataToSave)
if (this.props.isSessionStorage) {
window.sessionStorage.setItem(this.props.name, JSON.stringify(dataToSave))
} else {
window.localStorage.setItem(this.props.name, JSON.stringify(dataToSave))
}
}, this.props.debounce)
omitIgnoredFields = (data: FormikProps<any>) => {
const { ignoreFields } = this.props
LoggerInstance.log('omitted fields', ignoreFields)
const { values, touched, errors } = data
LoggerInstance.log('values', values, omit(values, ignoreFields))
return ignoreFields
? omit(
{
...data,
values: omit(values, ignoreFields),
touched: omit(touched, ignoreFields),
errors: omit(errors, ignoreFields)
},
ignoreFields
)
: data
}
componentDidUpdate(prevProps: PersistProps & { formik: FormikProps<any> }) {
if (!isEqual(prevProps.formik, this.props.formik)) {
this.saveForm(this.props.formik)
}
}
componentDidMount() {
const maybeState = this.props.isSessionStorage
? window.sessionStorage.getItem(this.props.name)
: window.localStorage.getItem(this.props.name)
if (maybeState && maybeState !== null) {
this.props.formik.setFormikState(JSON.parse(maybeState))
}
}
render(): null {
return null
}
}
export const Persist = connect<PersistProps, any>(PersistImpl)

View File

@ -19,5 +19,5 @@ export function NetworkIcon({ name }: { name: string }): ReactElement {
? EnergywebIcon ? EnergywebIcon
: EthIcon // ETH icon as fallback : EthIcon // ETH icon as fallback
return IconMapped ? <IconMapped className={styles.icon} /> : null return <IconMapped className={styles.icon} />
} }

View File

@ -0,0 +1,33 @@
import { render } from '@testing-library/react'
import React from 'react'
import NetworkName from './index'
describe('@shared/NetworkName', () => {
it('renders without crashing', () => {
render(<NetworkName networkId={1} />)
})
it('renders minimal', () => {
render(<NetworkName networkId={1} minimal />)
})
it('renders Polygon', () => {
render(<NetworkName networkId={137} />)
})
it('renders BSC', () => {
render(<NetworkName networkId={56} />)
})
it('renders Energy Web', () => {
render(<NetworkName networkId={246} />)
})
it('renders Moonriver', () => {
render(<NetworkName networkId={1285} />)
})
it('renders icon fallback', () => {
render(<NetworkName networkId={99999} />)
})
})

View File

@ -0,0 +1,20 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import Page from './index'
describe('@shared/Page', () => {
it('renders without crashing', () => {
render(
<Page uri="/hello" title="Hello Title" description="Hello Description">
Hello Children
</Page>
)
expect(screen.getByText('Hello Children')).toBeInTheDocument()
expect(screen.getByText('Hello Title')).toBeInTheDocument()
expect(screen.getByText('Hello Description')).toBeInTheDocument()
})
it('renders without title', () => {
render(<Page uri="/hello">Hello Children</Page>)
})
})

View File

@ -0,0 +1,28 @@
import testRender from '../../../../.jest/testRender'
import { render } from '@testing-library/react'
import React from 'react'
import Pagination from './index'
import { MAXIMUM_NUMBER_OF_PAGES_WITH_RESULTS } from '@utils/aquarius'
describe('@shared/Pagination', () => {
testRender(
<Pagination
totalPages={MAXIMUM_NUMBER_OF_PAGES_WITH_RESULTS + 1}
currentPage={2}
rowsPerPage={10}
rowCount={30}
onChangePage={() => jest.fn()}
/>
)
it('renders without currentPage prop', () => {
render(
<Pagination
totalPages={10}
rowsPerPage={10}
rowCount={30}
onChangePage={() => jest.fn()}
/>
)
})
})

View File

@ -0,0 +1,51 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import Price from './index'
import { asset } from '../../../../.jest/__fixtures__/assetWithAccessDetails'
import prices from '../../../../.jest/__fixtures__/prices'
jest.mock('../../../@context/Prices', () => ({
usePrices: () => prices,
getCoingeckoTokenId: () => 'ocean-protocol'
}))
describe('@shared/Price', () => {
it('renders fixed price', () => {
render(
<Price
accessDetails={{ ...asset.accessDetails, type: 'fixed', price: '10' }}
/>
)
expect(screen.getByText('10')).toBeInTheDocument()
})
it('renders free price', () => {
render(<Price accessDetails={{ ...asset.accessDetails, type: 'free' }} />)
expect(screen.getByText('Free')).toBeInTheDocument()
})
it('renders null price', () => {
render(<Price accessDetails={{ ...asset.accessDetails, price: null }} />)
expect(screen.getByText('-')).toBeInTheDocument()
})
it('renders conversion', async () => {
render(
<Price
accessDetails={{ ...asset.accessDetails, price: '10' }}
conversion
/>
)
expect(await screen.findByText('≈')).toBeInTheDocument()
})
it('renders no conversion when no price defined', async () => {
render(
<Price
accessDetails={{ ...asset.accessDetails, price: null }}
conversion
/>
)
expect(screen.queryByText('≈')).not.toBeInTheDocument()
})
})

View File

@ -7,7 +7,7 @@ const account = '0x0000000000000000000000000000000000000000'
jest.mock('axios') jest.mock('axios')
describe('Publisher', () => { describe('@shared/Publisher', () => {
test('should return correct markup by default', async () => { test('should return correct markup by default', async () => {
;(axios as any).get.mockImplementationOnce(() => ;(axios as any).get.mockImplementationOnce(() =>
Promise.resolve({ data: { name: 'jellymcjellyfish.eth' } }) Promise.resolve({ data: { name: 'jellymcjellyfish.eth' } })

View File

@ -0,0 +1,12 @@
import { render } from '@testing-library/react'
import React from 'react'
import testRender from '../../../../.jest/testRender'
import SuccessConfetti from './index'
describe('@shared/SuccessConfetti', () => {
testRender(<SuccessConfetti success="Nice Success!" />)
it('renders without success', () => {
render(<SuccessConfetti success={null} />)
})
})

View File

@ -0,0 +1,18 @@
import { render, fireEvent, screen } from '@testing-library/react'
import React from 'react'
import WalletNetworkSwitcher from './'
jest.mock('../../../@utils/web3', () => ({
addCustomNetwork: () => jest.fn()
}))
describe('@shared/WalletNetworkSwitcher', () => {
it('renders without crashing', () => {
render(<WalletNetworkSwitcher />)
})
it('switching networks can be invoked', () => {
render(<WalletNetworkSwitcher />)
fireEvent.click(screen.getByRole('button'))
})
})

View File

@ -13,6 +13,7 @@ export default function WalletNetworkSwitcher(): ReactElement {
const { networkId, web3Provider } = useWeb3() const { networkId, web3Provider } = useWeb3()
const { asset } = useAsset() const { asset } = useAsset()
const { networksList } = useNetworkMetadata() const { networksList } = useNetworkMetadata()
const ddoNetworkData = getNetworkDataById(networksList, asset.chainId) const ddoNetworkData = getNetworkDataById(networksList, asset.chainId)
const walletNetworkData = getNetworkDataById(networksList, networkId) const walletNetworkData = getNetworkDataById(networksList, networkId)

View File

@ -0,0 +1,52 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import Web3Feedback from './'
import { useGraphSyncStatus } from '../../../@hooks/useGraphSyncStatus'
jest.mock('../../../@hooks/useGraphSyncStatus')
describe('@shared/Web3Feedback', () => {
;(useGraphSyncStatus as jest.Mock).mockImplementation(() => ({
isGraphSynced: true,
blockGraph: '333333',
blockHead: '333333'
}))
it('renders without crashing', () => {
render(<Web3Feedback networkId={1} accountId="0xxxx" />)
})
it('renders isAssetNetwork === false', async () => {
render(
<Web3Feedback networkId={1} accountId="0xxxx" isAssetNetwork={false} />
)
expect(
await screen.findByText('Not connected to asset network')
).toBeInTheDocument()
})
it('renders isGraphSynced === false', async () => {
;(useGraphSyncStatus as jest.Mock).mockImplementation(() => ({
isGraphSynced: false
}))
render(
<Web3Feedback networkId={1} accountId="0xxxx" isAssetNetwork={true} />
)
expect(await screen.findByText('Data out of sync')).toBeInTheDocument()
})
it('renders no account', async () => {
render(<Web3Feedback networkId={1} accountId={undefined} />)
expect(await screen.findByText('No account connected')).toBeInTheDocument()
})
it('do nothing if nothing to show', async () => {
;(useGraphSyncStatus as jest.Mock).mockImplementation(() => ({
isGraphSynced: true
}))
render(
<Web3Feedback networkId={1} accountId="0xxxx" isAssetNetwork={true} />
)
expect(screen.queryByRole('heading')).not.toBeInTheDocument()
})
})

View File

@ -13,12 +13,13 @@ export declare type Web3Error = {
export default function Web3Feedback({ export default function Web3Feedback({
networkId, networkId,
accountId,
isAssetNetwork isAssetNetwork
}: { }: {
networkId: number networkId: number
accountId: string
isAssetNetwork?: boolean isAssetNetwork?: boolean
}): ReactElement { }): ReactElement {
const { accountId } = useWeb3()
const { isGraphSynced, blockGraph, blockHead } = useGraphSyncStatus(networkId) const { isGraphSynced, blockGraph, blockHead } = useGraphSyncStatus(networkId)
const [state, setState] = useState<string>() const [state, setState] = useState<string>()
const [title, setTitle] = useState<string>() const [title, setTitle] = useState<string>()

View File

@ -28,7 +28,7 @@ Full.args = {
state: 'info', state: 'info',
action: { action: {
name: 'Action', name: 'Action',
handleAction: () => null as any handleAction: () => null
}, },
badge: 'Hello', badge: 'Hello',
onDismiss: () => { onDismiss: () => {

View File

@ -1,7 +1,34 @@
import React from 'react' import React from 'react'
import testRender from '../../../../../.jest/testRender' import testRender from '../../../../../.jest/testRender'
import Alert from '@shared/atoms/Alert' import Alert from '@shared/atoms/Alert'
import { render } from '@testing-library/react'
describe('Alert', () => { describe('Alert', () => {
testRender(<Alert text="Alert text" state="info" />) testRender(
<Alert
title="Alert Title"
text="Alert text"
state="info"
badge="Hello"
action={{
name: 'Hello action',
style: 'text',
handleAction: () => null
}}
onDismiss={() => null}
/>
)
it('renders without action style', () => {
render(
<Alert
text="Alert text"
state="info"
action={{
name: 'Hello action',
handleAction: () => null
}}
/>
)
})
}) })

View File

@ -2,6 +2,15 @@ import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react' import { ComponentStory, ComponentMeta } from '@storybook/react'
import { ListItem } from '@shared/atoms/Lists' import { ListItem } from '@shared/atoms/Lists'
export const items = [
'List item short',
'List item long ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie',
'List item long ipsum dolor sit amet, consectetur adipiscing elit',
'List item short',
'List item long ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie',
'List item long ipsum dolor sit amet, consectetur adipiscing elit'
]
export default { export default {
title: 'Component/@shared/atoms/Lists', title: 'Component/@shared/atoms/Lists',
component: ListItem component: ListItem
@ -11,15 +20,6 @@ const Template: ComponentStory<typeof ListItem> = (args) => (
<ListItem {...args} /> <ListItem {...args} />
) )
const items = [
'List item short',
'List item long ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie',
'List item long ipsum dolor sit amet, consectetur adipiscing elit',
'List item short',
'List item long ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie',
'List item long ipsum dolor sit amet, consectetur adipiscing elit'
]
export const Unordered = Template.bind({}) export const Unordered = Template.bind({})
Unordered.decorators = [ Unordered.decorators = [
() => ( () => (

View File

@ -0,0 +1,28 @@
import { render } from '@testing-library/react'
import React from 'react'
import { ListItem } from './index'
import { items } from './index.stories'
describe('Lists', () => {
it('renders unordered', () => {
render(
<ul>
{items.map((item, key) => (
<ListItem key={key}>{item}</ListItem>
))}
</ul>
)
})
it('renders ordered', () => {
render(
<ol>
{items.map((item, key) => (
<ListItem ol key={key}>
{item}
</ListItem>
))}
</ol>
)
})
})

View File

@ -1,8 +1,18 @@
import React from 'react' import React from 'react'
import testRender from '../../../../../.jest/testRender' import testRender from '../../../../../.jest/testRender'
import Loader from '@shared/atoms/Loader' import Loader, { LoaderProps } from '@shared/atoms/Loader'
import { Default } from './index.stories' import { Default, WithMessage } from './index.stories'
import { render } from '@testing-library/react'
describe('Loader', () => { describe('Loader', () => {
testRender(<Loader {...Default.args} />) testRender(<Loader {...Default.args} />)
it('renders without wordmark', () => {
render(<Loader {...WithMessage.args} />)
})
it('renders white', () => {
const props: LoaderProps = { white: true }
render(<Loader {...props} />)
})
}) })

View File

@ -1,8 +1,13 @@
import React from 'react' import React from 'react'
import testRender from '../../../../../.jest/testRender' import testRender from '../../../../../.jest/testRender'
import Logo from '@shared/atoms/Logo' import Logo from '@shared/atoms/Logo'
import { Default } from './index.stories' import { Default, WithoutWordmark } from './index.stories'
import { render } from '@testing-library/react'
describe('Logo', () => { describe('Logo', () => {
testRender(<Logo {...Default.args} />) testRender(<Logo {...Default.args} />)
it('renders without wordmark', () => {
render(<Logo {...WithoutWordmark.args} />)
})
}) })

View File

@ -0,0 +1,23 @@
import React from 'react'
import { render } from '@testing-library/react'
import Modal from './'
import ReactModal from 'react-modal'
describe('Modal', () => {
it('renders without crashing', () => {
ReactModal.setAppElement(document.createElement('div'))
const { rerender } = render(
<Modal title="Hello" isOpen onToggleModal={() => null}>
Hello
</Modal>
)
expect(document.querySelector('.ReactModalPortal')).toBeInTheDocument()
rerender(
<Modal title="Hello" isOpen={false} onToggleModal={() => null}>
Hello
</Modal>
)
})
})

View File

@ -1,6 +1,26 @@
import React from 'react' import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react' import { ComponentStory, ComponentMeta } from '@storybook/react'
import Table, { TableOceanProps } from '@shared/atoms/Table' import Table, { TableOceanProps } from '@shared/atoms/Table'
import { columns, data } from '../../../../../.jest/__fixtures__/table'
export const args: TableOceanProps<any> = { columns, data }
export const argsWithPagination: TableOceanProps<any> = {
columns,
data: data.flatMap((i) => [i, i, i])
}
export const argsLoading: TableOceanProps<any> = {
isLoading: true,
columns: [],
data: []
}
export const argsEmpty: TableOceanProps<any> = {
emptyMessage: 'I am empty',
columns: [],
data: []
}
export default { export default {
title: 'Component/@shared/atoms/Table', title: 'Component/@shared/atoms/Table',
@ -13,70 +33,14 @@ interface Props {
args: TableOceanProps<any> args: TableOceanProps<any>
} }
const columns = [
{
name: 'Name',
selector: (row: any) => row.name,
maxWidth: '45rem',
grow: 1
},
{
name: 'Symbol',
selector: (row: any) => row.symbol,
maxWidth: '10rem'
},
{
name: 'Price',
selector: (row: any) => row.price,
right: true
}
]
const data = [
{
name: 'Title asset',
symbol: 'DATA-70',
price: '1.011'
},
{
name: 'Title asset Title asset Title asset Title asset Title asset',
symbol: 'DATA-71',
price: '1.011'
},
{
name: 'Title asset',
symbol: 'DATA-72',
price: '1.011'
},
{
name: 'Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset',
symbol: 'DATA-71',
price: '1.011'
}
]
export const WithData: Props = Template.bind({}) export const WithData: Props = Template.bind({})
WithData.args = { WithData.args = args
columns,
data
}
export const WithPagination: Props = Template.bind({}) export const WithPagination: Props = Template.bind({})
WithPagination.args = { WithPagination.args = argsWithPagination
columns,
data: data.flatMap((i) => [i, i, i])
}
export const Loading: Props = Template.bind({}) export const Loading: Props = Template.bind({})
Loading.args = { Loading.args = argsLoading
isLoading: true,
columns: [],
data: []
}
export const Empty: Props = Template.bind({}) export const Empty: Props = Template.bind({})
Empty.args = { Empty.args = argsEmpty
emptyMessage: 'I am empty',
columns: [],
data: []
}

View File

@ -0,0 +1,34 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import Table, { TableOceanProps } from '.'
import {
args,
argsWithPagination,
argsLoading,
argsEmpty
} from './index.stories'
describe('Table', () => {
it('renders without crashing', () => {
render(<Table {...args} />)
})
it('renders WithPagination', () => {
render(<Table {...argsWithPagination} />)
})
it('renders Loading', () => {
render(<Table {...argsLoading} />)
})
it('renders Empty', () => {
render(<Table {...argsEmpty} />)
expect(screen.getByText('I am empty')).toBeInTheDocument()
})
it('renders Empty without message', () => {
const args: TableOceanProps<any> = { ...argsEmpty, emptyMessage: undefined }
render(<Table {...args} />)
expect(screen.getByText('No results found')).toBeInTheDocument()
})
})

Some files were not shown because too many files have changed in this diff Show More