mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge branch 'main' into fix/issue-1069-c2d-unsupported-networks
This commit is contained in:
commit
606a32065c
71
.jest/__fixtures__/assetAquarius.ts
Normal file
71
.jest/__fixtures__/assetAquarius.ts
Normal 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: ''
|
||||
}
|
||||
}
|
26
.jest/__fixtures__/assetWithAccessDetails.ts
Normal file
26
.jest/__fixtures__/assetWithAccessDetails.ts
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
1538
.jest/__fixtures__/assetsWithAccessDetails.ts
Normal file
1538
.jest/__fixtures__/assetsWithAccessDetails.ts
Normal file
File diff suppressed because it is too large
Load Diff
64
.jest/__fixtures__/prices.ts
Normal file
64
.jest/__fixtures__/prices.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
41
.jest/__fixtures__/table.ts
Normal file
41
.jest/__fixtures__/table.ts
Normal 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'
|
||||
}
|
||||
]
|
10
.jest/__fixtures__/userPreferences.ts
Normal file
10
.jest/__fixtures__/userPreferences.ts
Normal 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
|
||||
}
|
33
.jest/__fixtures__/web3.ts
Normal file
33
.jest/__fixtures__/web3.ts
Normal 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: ''
|
||||
}
|
||||
}
|
3
.jest/__mocks__/@utils/accessDetailsAndPricing.ts
Normal file
3
.jest/__mocks__/@utils/accessDetailsAndPricing.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { assets } from '../../__fixtures__/assetsWithAccessDetails'
|
||||
|
||||
export const getAccessDetailsForAssets = jest.fn().mockResolvedValue(assets)
|
20
.jest/__mocks__/hooksMocks.ts
Normal file
20
.jest/__mocks__/hooksMocks.ts
Normal file
@ -0,0 +1,20 @@
|
||||
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', () => ({
|
||||
useMarketMetadata: () => marketMetadata
|
||||
}))
|
||||
|
||||
jest.mock('../../src/@context/UserPreferences', () => ({
|
||||
useUserPreferences: () => userPreferences
|
||||
}))
|
||||
|
||||
jest.mock('../../src/@context/Web3', () => ({
|
||||
useWeb3: () => web3
|
||||
}))
|
||||
|
||||
jest.mock('../../../@context/Asset', () => ({
|
||||
useAsset: () => ({ asset })
|
||||
}))
|
1
.jest/__mocks__/is-url-superb/index.ts
Normal file
1
.jest/__mocks__/is-url-superb/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default true
|
@ -1,7 +1,3 @@
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
import './__mocks__/matchMedia'
|
||||
import marketMetadataMock from './__mocks__/MarketMetadata'
|
||||
|
||||
jest.mock('../../src/@context/MarketMetadata', () => ({
|
||||
useMarketMetadata: () => marketMetadataMock
|
||||
}))
|
||||
import './__mocks__/hooksMocks'
|
||||
|
7589
package-lock.json
generated
7589
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@ -17,7 +17,7 @@
|
||||
"format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write",
|
||||
"type-check": "tsc --noEmit",
|
||||
"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/",
|
||||
"storybook": "cross-env NODE_ENV=test start-storybook -p 6006 --quiet",
|
||||
"storybook:build": "cross-env NODE_ENV=test build-storybook"
|
||||
@ -43,8 +43,6 @@
|
||||
"gray-matter": "^4.0.3",
|
||||
"is-url-superb": "^6.1.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"match-sorter": "^6.3.1",
|
||||
"myetherwallet-blockies": "^0.1.1",
|
||||
"next": "12.3.1",
|
||||
@ -72,8 +70,8 @@
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^6.5.12",
|
||||
"@storybook/builder-webpack5": "^6.5.12",
|
||||
"@storybook/addon-essentials": "^6.5.13",
|
||||
"@storybook/builder-webpack5": "^6.5.13",
|
||||
"@storybook/manager-webpack5": "^6.5.13",
|
||||
"@storybook/react": "^6.5.13",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
@ -81,26 +79,24 @@
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@types/js-cookie": "^3.0.2",
|
||||
"@types/loadable__component": "^5.13.4",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/lodash.omit": "^4.5.7",
|
||||
"@types/node": "^18.7.18",
|
||||
"@types/node": "^18.8.5",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-paginate": "^7.1.1",
|
||||
"@types/remove-markdown": "^0.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.38.1",
|
||||
"@typescript-eslint/parser": "^5.38.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
||||
"@typescript-eslint/parser": "^5.42.0",
|
||||
"apollo": "^2.34.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.23.1",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint-config-oceanprotocol": "^2.0.4",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-jest-dom": "^4.0.2",
|
||||
"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-testing-library": "^5.7.0",
|
||||
"eslint-plugin-testing-library": "^5.7.2",
|
||||
"https-browserify": "^1.0.0",
|
||||
"husky": "^8.0.1",
|
||||
"jest": "^29.1.2",
|
||||
@ -111,14 +107,17 @@
|
||||
"serve": "^14.0.1",
|
||||
"stream-http": "^3.2.0",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"typescript": "^4.8.3"
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"overrides": {
|
||||
"graphql": "15.8.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/oceanprotocol/market"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "16"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
|
@ -323,7 +323,7 @@ export async function getAccessDetailsForAssets(
|
||||
},
|
||||
queryContext
|
||||
)
|
||||
tokenQueryResult.data?.tokens.forEach((token) => {
|
||||
tokenQueryResult?.data?.tokens?.forEach((token) => {
|
||||
const currentAsset = assetsExtended.find(
|
||||
(asset) =>
|
||||
asset.services[0].datatokenAddress.toLowerCase() === token.id
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
||||
import { metadataCacheUri } from '../../app.config'
|
||||
@ -291,7 +291,7 @@ export async function getAlgorithmDatasetsForCompute(
|
||||
must: {
|
||||
match: {
|
||||
'services.compute.publisherTrustedAlgorithms.did': {
|
||||
query: escapeEsReservedCharacters(algorithmId)
|
||||
query: algorithmId
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -304,7 +304,6 @@ export async function getAlgorithmDatasetsForCompute(
|
||||
|
||||
const query = generateBaseQuery(baseQueryParams)
|
||||
const computeDatasets = await queryMetadata(query, cancelToken)
|
||||
|
||||
if (computeDatasets?.totalResults === 0) return []
|
||||
|
||||
const datasets = await transformAssetToAssetSelection(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getAccessDetailsForAssets } from './accessDetailsAndPricing'
|
||||
import { PublisherTrustedAlgorithm, Asset } from '@oceanprotocol/lib'
|
||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
||||
import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
|
||||
import { getServiceByName } from './ddo'
|
||||
|
||||
export async function transformAssetToAssetSelection(
|
||||
@ -14,10 +14,12 @@ export async function transformAssetToAssetSelection(
|
||||
const algorithmList: AssetSelectionAsset[] = []
|
||||
|
||||
for (const asset of extendedAssets) {
|
||||
const algoComputeService = getServiceByName(asset, 'compute')
|
||||
const algoService =
|
||||
getServiceByName(asset, 'compute') || getServiceByName(asset, 'access')
|
||||
|
||||
if (
|
||||
asset?.accessDetails?.price &&
|
||||
algoComputeService?.serviceEndpoint === datasetProviderEndpoint
|
||||
algoService?.serviceEndpoint === datasetProviderEndpoint
|
||||
) {
|
||||
let selected = false
|
||||
selectedAlgorithms?.forEach((algorithm: PublisherTrustedAlgorithm) => {
|
||||
|
@ -21,10 +21,10 @@ import {
|
||||
} from './aquarius'
|
||||
import { fetchDataForMultipleChains } from './subgraph'
|
||||
import { getServiceById, getServiceByName } from './ddo'
|
||||
import { SortTermOptions } from 'src/@types/aquarius/SearchQuery'
|
||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
||||
import { SortTermOptions } from '../@types/aquarius/SearchQuery'
|
||||
import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
|
||||
import { transformAssetToAssetSelection } from './assetConvertor'
|
||||
import { ComputeEditForm } from 'src/components/Asset/Edit/_types'
|
||||
import { ComputeEditForm } from '../components/Asset/Edit/_types'
|
||||
import { getFileDidInfo } from './provider'
|
||||
|
||||
const getComputeOrders = gql`
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from '@hooks/useNetworkMetadata'
|
||||
import { getAssetsFromNftList } from './aquarius'
|
||||
import { chainIdsSupported } from 'app.config'
|
||||
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
||||
import { Asset } from '@oceanprotocol/lib'
|
||||
|
||||
const AllLocked = gql`
|
||||
query AllLocked {
|
||||
@ -42,7 +42,7 @@ const NftOwnAllocation = gql`
|
||||
}
|
||||
`
|
||||
const OceanLocked = gql`
|
||||
query OceanLocked($address: String) {
|
||||
query OceanLocked($address: ID!) {
|
||||
veOCEAN(id: $address) {
|
||||
id
|
||||
lockedAmount
|
||||
|
@ -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;
|
||||
}
|
29
src/components/@shared/AddToken/index.stories.tsx
Normal file
29
src/components/@shared/AddToken/index.stories.tsx
Normal 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
|
||||
}
|
24
src/components/@shared/AddToken/index.test.tsx
Normal file
24
src/components/@shared/AddToken/index.test.tsx
Normal 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 />)
|
||||
})
|
||||
})
|
@ -8,19 +8,21 @@ import styles from './index.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export interface AddTokenProps {
|
||||
address: string
|
||||
symbol: string
|
||||
text?: string
|
||||
className?: string
|
||||
minimal?: boolean
|
||||
}
|
||||
|
||||
export default function AddToken({
|
||||
address,
|
||||
symbol,
|
||||
text,
|
||||
className,
|
||||
minimal
|
||||
}: {
|
||||
address: string
|
||||
symbol: string
|
||||
text?: string
|
||||
className?: string
|
||||
minimal?: boolean
|
||||
}): ReactElement {
|
||||
}: AddTokenProps): ReactElement {
|
||||
const { web3Provider } = useWeb3()
|
||||
|
||||
const styleClasses = cx({
|
||||
|
65
src/components/@shared/AnnouncementBanner/index.stories.tsx
Normal file
65
src/components/@shared/AnnouncementBanner/index.stories.tsx
Normal 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!')
|
||||
}
|
||||
}
|
||||
}
|
13
src/components/@shared/AnnouncementBanner/index.test.tsx
Normal file
13
src/components/@shared/AnnouncementBanner/index.test.tsx
Normal 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"
|
||||
/>
|
||||
)
|
||||
})
|
@ -12,17 +12,19 @@ export interface AnnouncementAction {
|
||||
handleAction: () => void
|
||||
}
|
||||
|
||||
export interface AnnouncementBannerProps {
|
||||
text: string
|
||||
action?: AnnouncementAction
|
||||
state?: 'success' | 'warning' | 'error'
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function AnnouncementBanner({
|
||||
text,
|
||||
action,
|
||||
state,
|
||||
className
|
||||
}: {
|
||||
text: string
|
||||
action?: AnnouncementAction
|
||||
state?: 'success' | 'warning' | 'error'
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
}: AnnouncementBannerProps): ReactElement {
|
||||
const styleClasses = cx({
|
||||
banner: true,
|
||||
error: state === 'error',
|
||||
|
@ -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';
|
||||
}
|
35
src/components/@shared/AssetList/index.stories.tsx
Normal file
35
src/components/@shared/AssetList/index.stories.tsx
Normal 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
|
||||
}
|
32
src/components/@shared/AssetList/index.test.tsx
Normal file
32
src/components/@shared/AssetList/index.test.tsx
Normal 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 />)
|
||||
})
|
||||
})
|
@ -2,15 +2,11 @@ import AssetTeaser from '@shared/AssetTeaser'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Pagination from '@shared/Pagination'
|
||||
import styles from './index.module.css'
|
||||
import classNames from 'classnames/bind'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import { useIsMounted } from '@hooks/useIsMounted'
|
||||
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
function LoaderArea() {
|
||||
return (
|
||||
<div className={styles.loaderWrap}>
|
||||
@ -19,7 +15,7 @@ function LoaderArea() {
|
||||
)
|
||||
}
|
||||
|
||||
declare type AssetListProps = {
|
||||
export declare type AssetListProps = {
|
||||
assets: AssetExtended[]
|
||||
showPagination: boolean
|
||||
page?: number
|
||||
@ -28,6 +24,8 @@ declare type AssetListProps = {
|
||||
onPageChange?: React.Dispatch<React.SetStateAction<number>>
|
||||
className?: string
|
||||
noPublisher?: boolean
|
||||
noDescription?: boolean
|
||||
noPrice?: boolean
|
||||
}
|
||||
|
||||
export default function AssetList({
|
||||
@ -38,16 +36,18 @@ export default function AssetList({
|
||||
isLoading,
|
||||
onPageChange,
|
||||
className,
|
||||
noPublisher
|
||||
noPublisher,
|
||||
noDescription,
|
||||
noPrice
|
||||
}: AssetListProps): ReactElement {
|
||||
const { chainIds } = useUserPreferences()
|
||||
const { accountId } = useWeb3()
|
||||
const [assetsWithPrices, setAssetsWithPrices] = useState<AssetExtended[]>()
|
||||
const [assetsWithPrices, setAssetsWithPrices] =
|
||||
useState<AssetExtended[]>(assets)
|
||||
const [loading, setLoading] = useState<boolean>(isLoading)
|
||||
const isMounted = useIsMounted()
|
||||
|
||||
useEffect(() => {
|
||||
if (!assets) return
|
||||
if (!assets || !assets.length) return
|
||||
|
||||
setAssetsWithPrices(assets as AssetExtended[])
|
||||
setLoading(false)
|
||||
@ -67,16 +67,9 @@ export default function AssetList({
|
||||
onPageChange(selected + 1)
|
||||
}
|
||||
|
||||
const styleClasses = cx({
|
||||
assetList: true,
|
||||
[className]: className
|
||||
})
|
||||
const styleClasses = `${styles.assetList} ${className || ''}`
|
||||
|
||||
return chainIds.length === 0 ? (
|
||||
<div className={styleClasses}>
|
||||
<div className={styles.empty}>No network selected</div>
|
||||
</div>
|
||||
) : assetsWithPrices && !loading ? (
|
||||
return assetsWithPrices && !loading ? (
|
||||
<>
|
||||
<div className={styleClasses}>
|
||||
{assetsWithPrices.length > 0 ? (
|
||||
@ -85,6 +78,8 @@ export default function AssetList({
|
||||
asset={assetWithPrice}
|
||||
key={assetWithPrice.id}
|
||||
noPublisher={noPublisher}
|
||||
noDescription={noDescription}
|
||||
noPrice={noPrice}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
24
src/components/@shared/AssetListTitle/index.test.tsx
Normal file
24
src/components/@shared/AssetListTitle/index.test.tsx
Normal 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" />
|
||||
)
|
||||
})
|
||||
})
|
@ -1,7 +1,7 @@
|
||||
import Link from 'next/link'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { getAssetsNames } from '@utils/aquarius'
|
||||
import styles from './AssetListTitle.module.css'
|
||||
import styles from './index.module.css'
|
||||
import axios from 'axios'
|
||||
import { Asset } from '@oceanprotocol/lib'
|
||||
import { useMarketMetadata } from '@context/MarketMetadata'
|
@ -21,7 +21,7 @@
|
||||
}
|
||||
|
||||
.detailLine {
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.content {
|
||||
@ -43,8 +43,12 @@
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.price {
|
||||
margin-top: calc(var(--spacer) / 12);
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: calc(var(--spacer) / 4);
|
||||
margin-top: calc(var(--spacer) / 12);
|
||||
}
|
||||
|
||||
.typeLabel {
|
||||
|
8
src/components/@shared/AssetTeaser/index.test.tsx
Normal file
8
src/components/@shared/AssetTeaser/index.test.tsx
Normal 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} />)
|
||||
})
|
@ -11,14 +11,18 @@ import { getServiceByName } from '@utils/ddo'
|
||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
|
||||
declare type AssetTeaserProps = {
|
||||
export declare type AssetTeaserProps = {
|
||||
asset: AssetExtended
|
||||
noPublisher?: boolean
|
||||
noDescription?: boolean
|
||||
noPrice?: boolean
|
||||
}
|
||||
|
||||
export default function AssetTeaser({
|
||||
asset,
|
||||
noPublisher
|
||||
noPublisher,
|
||||
noDescription,
|
||||
noPrice
|
||||
}: AssetTeaserProps): ReactElement {
|
||||
const { name, type, description } = asset.metadata
|
||||
const { datatokens } = asset
|
||||
@ -53,16 +57,23 @@ export default function AssetTeaser({
|
||||
</Dotdotdot>
|
||||
{!noPublisher && <Publisher account={owner} minimal />}
|
||||
</header>
|
||||
<div className={styles.content}>
|
||||
<Dotdotdot tagName="p" clamp={3}>
|
||||
{removeMarkdown(description?.substring(0, 300) || '')}
|
||||
</Dotdotdot>
|
||||
</div>
|
||||
{isUnsupportedPricing || !asset.services.length ? (
|
||||
<strong>No pricing schema available</strong>
|
||||
) : (
|
||||
<Price accessDetails={asset.accessDetails} size="small" />
|
||||
{!noDescription && (
|
||||
<div className={styles.content}>
|
||||
<Dotdotdot tagName="p" clamp={3}>
|
||||
{removeMarkdown(description?.substring(0, 300) || '')}
|
||||
</Dotdotdot>
|
||||
</div>
|
||||
)}
|
||||
{!noPrice && (
|
||||
<div className={styles.price}>
|
||||
{isUnsupportedPricing || !asset.services.length ? (
|
||||
<strong>No pricing schema available</strong>
|
||||
) : (
|
||||
<Price accessDetails={asset.accessDetails} size="small" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<footer className={styles.footer}>
|
||||
{allocated && allocated > 0 ? (
|
||||
<span className={styles.typeLabel}>
|
||||
|
7
src/components/@shared/DebugOutput/index.test.tsx
Normal file
7
src/components/@shared/DebugOutput/index.test.tsx
Normal 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" />)
|
||||
})
|
21
src/components/@shared/ExplorerLink/index.test.tsx
Normal file
21
src/components/@shared/ExplorerLink/index.test.tsx
Normal 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')
|
||||
})
|
||||
})
|
@ -1,12 +1,9 @@
|
||||
import React, { ReactElement, ReactNode, useEffect, useState } from 'react'
|
||||
import External from '@images/external.svg'
|
||||
import classNames from 'classnames/bind'
|
||||
import { Config } from '@oceanprotocol/lib'
|
||||
import styles from './index.module.css'
|
||||
import { getOceanConfig } from '@utils/ocean'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export default function ExplorerLink({
|
||||
networkId,
|
||||
path,
|
||||
@ -20,10 +17,6 @@ export default function ExplorerLink({
|
||||
}): ReactElement {
|
||||
const [url, setUrl] = useState<string>()
|
||||
const [oceanConfig, setOceanConfig] = useState<Config>()
|
||||
const styleClasses = cx({
|
||||
link: true,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!networkId) return
|
||||
@ -39,7 +32,7 @@ export default function ExplorerLink({
|
||||
title={`View on ${oceanConfig?.explorerUri}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styleClasses}
|
||||
className={`${styles.link} ${className || ''}`}
|
||||
>
|
||||
{children} <External />
|
||||
</a>
|
||||
|
37
src/components/@shared/FileIcon/index.test.tsx
Normal file
37
src/components/@shared/FileIcon/index.test.tsx
Normal 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} />)
|
||||
})
|
||||
})
|
@ -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'
|
||||
}
|
21
src/components/@shared/FormInput/Error.test.tsx
Normal file
21
src/components/@shared/FormInput/Error.test.tsx
Normal 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()
|
||||
})
|
||||
})
|
@ -1,9 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Help.module.css'
|
||||
import Markdown from '@shared/Markdown'
|
||||
import classNames from 'classnames/bind'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
const FormHelp = ({
|
||||
children,
|
||||
@ -12,12 +9,9 @@ const FormHelp = ({
|
||||
children: string
|
||||
className?: string
|
||||
}): ReactElement => {
|
||||
const styleClasses = cx({
|
||||
help: true,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
return <Markdown className={styleClasses} text={children} />
|
||||
return (
|
||||
<Markdown className={`${styles.help} ${className || ''}`} text={children} />
|
||||
)
|
||||
}
|
||||
|
||||
export default FormHelp
|
||||
|
@ -58,11 +58,11 @@
|
||||
}
|
||||
|
||||
.radio {
|
||||
composes: radio from '@shared/FormInput/InputRadio.module.css';
|
||||
composes: radio from '@shared/FormInput/InputElement/Radio/index.module.css';
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
composes: checkbox from '@shared/FormInput/InputRadio.module.css';
|
||||
composes: checkbox from '@shared/FormInput/InputElement/Radio/index.module.css';
|
||||
}
|
||||
|
||||
.title {
|
@ -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 />)
|
||||
})
|
||||
})
|
@ -1,15 +1,12 @@
|
||||
import React, { ChangeEvent, useState } from 'react'
|
||||
import Dotdotdot from 'react-dotdotdot'
|
||||
import slugify from 'slugify'
|
||||
import classNames from 'classnames/bind'
|
||||
import PriceUnit from '@shared/Price/PriceUnit'
|
||||
import External from '@images/external.svg'
|
||||
import InputElement from '@shared/FormInput/InputElement'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export interface AssetSelectionAsset {
|
||||
did: string
|
||||
name: string
|
||||
@ -34,18 +31,19 @@ export default function AssetSelection({
|
||||
}): JSX.Element {
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
|
||||
const styleClassesInput = cx({
|
||||
input: true,
|
||||
[styles.checkbox]: multiple,
|
||||
[styles.radio]: !multiple
|
||||
})
|
||||
const styleClassesWrapper = `${styles.selection} ${
|
||||
disabled ? styles.disabled : ''
|
||||
}`
|
||||
const styleClassesInput = `${styles.input} ${
|
||||
multiple ? styles.checkbox : styles.radio
|
||||
}`
|
||||
|
||||
function handleSearchInput(e: ChangeEvent<HTMLInputElement>) {
|
||||
setSearchValue(e.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.selection} ${disabled ? styles.disabled : ''}`}>
|
||||
<div className={styleClassesWrapper}>
|
||||
<InputElement
|
||||
type="search"
|
||||
name="search"
|
@ -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} />)
|
||||
})
|
||||
})
|
@ -1,10 +1,7 @@
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export interface BoxSelectionOption {
|
||||
name: string
|
||||
value?: string
|
||||
@ -26,15 +23,10 @@ export default function BoxSelection({
|
||||
disabled?: boolean
|
||||
handleChange?: (event: ChangeEvent<HTMLInputElement>) => void
|
||||
}): JSX.Element {
|
||||
const styleClassesWrapper = cx({
|
||||
boxSelectionsWrapper: true,
|
||||
[styles.disabled]: disabled
|
||||
})
|
||||
|
||||
const styleClassesInput = cx({
|
||||
input: true,
|
||||
radio: true
|
||||
})
|
||||
const styleClassesWrapper = `${styles.boxSelectionsWrapper} ${
|
||||
disabled ? styles.disabled : ''
|
||||
}`
|
||||
const styleClassesInput = `${styles.input} ${styles.radio}`
|
||||
|
||||
return (
|
||||
<div className={styleClassesWrapper}>
|
||||
@ -51,7 +43,7 @@ export default function BoxSelection({
|
||||
type="radio"
|
||||
className={styleClassesInput}
|
||||
disabled={disabled}
|
||||
value={option.value ? option.value : option.name}
|
||||
value={option.value || option.name}
|
||||
name={name}
|
||||
/>
|
||||
<label
|
@ -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} />)
|
||||
})
|
||||
})
|
@ -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()
|
||||
})
|
||||
})
|
@ -1,17 +1,15 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import { useField, useFormikContext } from 'formik'
|
||||
import { useField } from 'formik'
|
||||
import FileInfo from './Info'
|
||||
import UrlInput from '../URLInput'
|
||||
import { InputProps } from '@shared/FormInput'
|
||||
import { getFileUrlInfo } from '@utils/provider'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import { useAsset } from '@context/Asset'
|
||||
|
||||
export default function FilesInput(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { values, setFieldError } = useFormikContext<FormPublishData>()
|
||||
const { asset } = useAsset()
|
||||
|
||||
async function handleValidation(e: React.SyntheticEvent, url: string) {
|
||||
@ -19,8 +17,8 @@ export default function FilesInput(props: InputProps): ReactElement {
|
||||
e?.preventDefault()
|
||||
|
||||
try {
|
||||
const providerUrl = values?.services
|
||||
? values?.services[0].providerUrl.url
|
||||
const providerUrl = props.form?.values?.services
|
||||
? props.form?.values?.services[0].providerUrl.url
|
||||
: asset.services[0].serviceEndpoint
|
||||
setIsLoading(true)
|
||||
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
|
||||
helpers.setValue([{ url, ...checkedFile[0] }])
|
||||
} catch (error) {
|
||||
setFieldError(`${field.name}[0].url`, error.message)
|
||||
props.form.setFieldError(`${field.name}[0].url`, error.message)
|
||||
LoggerInstance.error(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
@ -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}`
|
||||
}
|
@ -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} />)
|
||||
})
|
||||
})
|
@ -16,7 +16,7 @@
|
||||
|
||||
.radio,
|
||||
.checkbox {
|
||||
composes: input from './InputElement.module.css';
|
||||
composes: input from '../index.module.css';
|
||||
position: relative;
|
||||
padding: 0;
|
||||
width: 18px;
|
@ -1,7 +1,7 @@
|
||||
import React, { InputHTMLAttributes, ReactElement } from 'react'
|
||||
import slugify from 'slugify'
|
||||
import classNames from 'classnames/bind'
|
||||
import styles from './InputRadio.module.css'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
@ -2,11 +2,11 @@ import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import CreatableSelect from 'react-select/creatable'
|
||||
import { OnChangeValue } from 'react-select'
|
||||
import { useField } from 'formik'
|
||||
import { InputProps } from '.'
|
||||
import { InputProps } from '../..'
|
||||
import { getTagsList } from '@utils/aquarius'
|
||||
import { chainIds } from 'app.config'
|
||||
import { chainIds } from '../../../../../../app.config'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import styles from './TagsAutoComplete.module.css'
|
||||
import styles from './index.module.css'
|
||||
import { matchSorter } from 'match-sorter'
|
||||
|
||||
interface AutoCompleteOption {
|
@ -1,5 +1,5 @@
|
||||
.input {
|
||||
composes: input from '@shared/FormInput/InputElement.module.css';
|
||||
composes: input from '@shared/FormInput/InputElement/index.module.css';
|
||||
}
|
||||
|
||||
.hasError {
|
@ -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'))
|
||||
})
|
||||
})
|
@ -7,6 +7,14 @@ import InputGroup from '@shared/FormInput/InputGroup'
|
||||
import InputElement from '@shared/FormInput/InputElement'
|
||||
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({
|
||||
submitText,
|
||||
handleButtonClick,
|
||||
@ -14,13 +22,7 @@ export default function URLInput({
|
||||
name,
|
||||
checkUrl,
|
||||
...props
|
||||
}: {
|
||||
submitText: string
|
||||
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
||||
isLoading: boolean
|
||||
name: string
|
||||
checkUrl?: boolean
|
||||
}): ReactElement {
|
||||
}: URLInputProps): ReactElement {
|
||||
const [field, meta] = useField(name)
|
||||
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
|
||||
|
@ -1,17 +1,15 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './InputElement.module.css'
|
||||
import { InputProps } from '.'
|
||||
import FilesInput from '../FormFields/FilesInput'
|
||||
import CustomProvider from '../FormFields/Provider'
|
||||
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
|
||||
import Datatoken from '../FormFields/Datatoken'
|
||||
import styles from './index.module.css'
|
||||
import { InputProps } from '..'
|
||||
import FilesInput from './FilesInput'
|
||||
import CustomProvider from './Provider'
|
||||
import BoxSelection, { BoxSelectionOption } from './BoxSelection'
|
||||
import Datatoken from './Datatoken'
|
||||
import classNames from 'classnames/bind'
|
||||
import AssetSelection, {
|
||||
AssetSelectionAsset
|
||||
} from '../FormFields/AssetSelection'
|
||||
import Nft from '../FormFields/Nft'
|
||||
import InputRadio from './InputRadio'
|
||||
import ContainerInput from '@shared/FormFields/ContainerInput'
|
||||
import AssetSelection, { AssetSelectionAsset } from './AssetSelection'
|
||||
import Nft from './Nft'
|
||||
import InputRadio from './Radio'
|
||||
import ContainerInput from '@shared/FormInput/InputElement/ContainerInput'
|
||||
import TagsAutoComplete from './TagsAutoComplete'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
@ -87,12 +85,14 @@ export default function InputElement({
|
||||
|
||||
case 'radio':
|
||||
case 'checkbox':
|
||||
return <InputRadio options={options} inputSize={size} {...props} />
|
||||
return (
|
||||
<InputRadio options={options as string[]} inputSize={size} {...props} />
|
||||
)
|
||||
|
||||
case 'assetSelection':
|
||||
return (
|
||||
<AssetSelection
|
||||
assets={options as unknown as AssetSelectionAsset[]}
|
||||
assets={options as AssetSelectionAsset[]}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
@ -101,14 +101,14 @@ export default function InputElement({
|
||||
case 'assetSelectionMultiple':
|
||||
return (
|
||||
<AssetSelection
|
||||
assets={options as unknown as AssetSelectionAsset[]}
|
||||
assets={options as AssetSelectionAsset[]}
|
||||
multiple
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
case 'files':
|
||||
return <FilesInput {...field} {...props} />
|
||||
return <FilesInput {...field} form={form} {...props} />
|
||||
case 'container':
|
||||
return <ContainerInput {...field} {...props} />
|
||||
case 'providerUrl':
|
||||
@ -120,7 +120,7 @@ export default function InputElement({
|
||||
case 'boxSelection':
|
||||
return (
|
||||
<BoxSelection
|
||||
options={options as unknown as BoxSelectionOption[]}
|
||||
options={options as BoxSelectionOption[]}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
@ -1,3 +0,0 @@
|
||||
.row {
|
||||
margin-bottom: var(--spacer);
|
||||
}
|
@ -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
|
105
src/components/@shared/FormInput/index.test.tsx
Normal file
105
src/components/@shared/FormInput/index.test.tsx
Normal 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()}
|
||||
/>
|
||||
)
|
||||
})
|
||||
})
|
@ -16,6 +16,8 @@ import Disclaimer from './Disclaimer'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import Markdown from '@shared/Markdown'
|
||||
import FormHelp from './Help'
|
||||
import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
|
||||
import { BoxSelectionOption } from '@shared/FormInput/InputElement/BoxSelection'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
@ -28,7 +30,7 @@ export interface InputProps {
|
||||
prominentHelp?: boolean
|
||||
tag?: string
|
||||
type?: string
|
||||
options?: string[]
|
||||
options?: string[] | AssetSelectionAsset[] | BoxSelectionOption[]
|
||||
sortOptions?: boolean
|
||||
additionalComponent?: ReactElement
|
||||
value?: string | number
|
||||
|
@ -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)
|
@ -19,5 +19,5 @@ export function NetworkIcon({ name }: { name: string }): ReactElement {
|
||||
? EnergywebIcon
|
||||
: EthIcon // ETH icon as fallback
|
||||
|
||||
return IconMapped ? <IconMapped className={styles.icon} /> : null
|
||||
return <IconMapped className={styles.icon} />
|
||||
}
|
||||
|
33
src/components/@shared/NetworkName/index.test.tsx
Normal file
33
src/components/@shared/NetworkName/index.test.tsx
Normal 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} />)
|
||||
})
|
||||
})
|
20
src/components/@shared/Page/index.test.tsx
Normal file
20
src/components/@shared/Page/index.test.tsx
Normal 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>)
|
||||
})
|
||||
})
|
28
src/components/@shared/Pagination/index.test.tsx
Normal file
28
src/components/@shared/Pagination/index.test.tsx
Normal 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()}
|
||||
/>
|
||||
)
|
||||
})
|
||||
})
|
51
src/components/@shared/Price/index.test.tsx
Normal file
51
src/components/@shared/Price/index.test.tsx
Normal 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()
|
||||
})
|
||||
})
|
@ -7,7 +7,7 @@ const account = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
describe('Publisher', () => {
|
||||
describe('@shared/Publisher', () => {
|
||||
test('should return correct markup by default', async () => {
|
||||
;(axios as any).get.mockImplementationOnce(() =>
|
||||
Promise.resolve({ data: { name: 'jellymcjellyfish.eth' } })
|
||||
|
12
src/components/@shared/SuccessConfetti/index.test.tsx
Normal file
12
src/components/@shared/SuccessConfetti/index.test.tsx
Normal 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} />)
|
||||
})
|
||||
})
|
18
src/components/@shared/WalletNetworkSwitcher/index.test.tsx
Normal file
18
src/components/@shared/WalletNetworkSwitcher/index.test.tsx
Normal 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'))
|
||||
})
|
||||
})
|
@ -13,6 +13,7 @@ export default function WalletNetworkSwitcher(): ReactElement {
|
||||
const { networkId, web3Provider } = useWeb3()
|
||||
const { asset } = useAsset()
|
||||
const { networksList } = useNetworkMetadata()
|
||||
|
||||
const ddoNetworkData = getNetworkDataById(networksList, asset.chainId)
|
||||
const walletNetworkData = getNetworkDataById(networksList, networkId)
|
||||
|
||||
|
52
src/components/@shared/Web3Feedback/index.test.tsx
Normal file
52
src/components/@shared/Web3Feedback/index.test.tsx
Normal 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()
|
||||
})
|
||||
})
|
@ -13,12 +13,13 @@ export declare type Web3Error = {
|
||||
|
||||
export default function Web3Feedback({
|
||||
networkId,
|
||||
accountId,
|
||||
isAssetNetwork
|
||||
}: {
|
||||
networkId: number
|
||||
accountId: string
|
||||
isAssetNetwork?: boolean
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { isGraphSynced, blockGraph, blockHead } = useGraphSyncStatus(networkId)
|
||||
const [state, setState] = useState<string>()
|
||||
const [title, setTitle] = useState<string>()
|
||||
|
@ -28,7 +28,7 @@ Full.args = {
|
||||
state: 'info',
|
||||
action: {
|
||||
name: 'Action',
|
||||
handleAction: () => null as any
|
||||
handleAction: () => null
|
||||
},
|
||||
badge: 'Hello',
|
||||
onDismiss: () => {
|
||||
|
@ -1,7 +1,34 @@
|
||||
import React from 'react'
|
||||
import testRender from '../../../../../.jest/testRender'
|
||||
import Alert from '@shared/atoms/Alert'
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
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
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -2,6 +2,15 @@ import React from 'react'
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react'
|
||||
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 {
|
||||
title: 'Component/@shared/atoms/Lists',
|
||||
component: ListItem
|
||||
@ -11,15 +20,6 @@ const Template: ComponentStory<typeof 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({})
|
||||
Unordered.decorators = [
|
||||
() => (
|
||||
|
28
src/components/@shared/atoms/Lists/index.test.tsx
Normal file
28
src/components/@shared/atoms/Lists/index.test.tsx
Normal 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>
|
||||
)
|
||||
})
|
||||
})
|
@ -1,8 +1,18 @@
|
||||
import React from 'react'
|
||||
import testRender from '../../../../../.jest/testRender'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import { Default } from './index.stories'
|
||||
import Loader, { LoaderProps } from '@shared/atoms/Loader'
|
||||
import { Default, WithMessage } from './index.stories'
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
describe('Loader', () => {
|
||||
testRender(<Loader {...Default.args} />)
|
||||
|
||||
it('renders without wordmark', () => {
|
||||
render(<Loader {...WithMessage.args} />)
|
||||
})
|
||||
|
||||
it('renders white', () => {
|
||||
const props: LoaderProps = { white: true }
|
||||
render(<Loader {...props} />)
|
||||
})
|
||||
})
|
||||
|
@ -1,8 +1,13 @@
|
||||
import React from 'react'
|
||||
import testRender from '../../../../../.jest/testRender'
|
||||
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', () => {
|
||||
testRender(<Logo {...Default.args} />)
|
||||
|
||||
it('renders without wordmark', () => {
|
||||
render(<Logo {...WithoutWordmark.args} />)
|
||||
})
|
||||
})
|
||||
|
23
src/components/@shared/atoms/Modal/index.test.tsx
Normal file
23
src/components/@shared/atoms/Modal/index.test.tsx
Normal 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>
|
||||
)
|
||||
})
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user