mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge branch 'main' into fix/issue-validation-g-drive-links
This commit is contained in:
commit
ceb055af36
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,23 +1,3 @@
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
import './__mocks__/matchMedia'
|
||||
|
||||
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 })
|
||||
}))
|
||||
import './__mocks__/hooksMocks'
|
||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -97,7 +97,7 @@
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "16"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
|
@ -24,6 +24,8 @@ export declare type AssetListProps = {
|
||||
onPageChange?: React.Dispatch<React.SetStateAction<number>>
|
||||
className?: string
|
||||
noPublisher?: boolean
|
||||
noDescription?: boolean
|
||||
noPrice?: boolean
|
||||
}
|
||||
|
||||
export default function AssetList({
|
||||
@ -34,7 +36,9 @@ export default function AssetList({
|
||||
isLoading,
|
||||
onPageChange,
|
||||
className,
|
||||
noPublisher
|
||||
noPublisher,
|
||||
noDescription,
|
||||
noPrice
|
||||
}: AssetListProps): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const [assetsWithPrices, setAssetsWithPrices] =
|
||||
@ -74,6 +78,8 @@ export default function AssetList({
|
||||
asset={assetWithPrice}
|
||||
key={assetWithPrice.id}
|
||||
noPublisher={noPublisher}
|
||||
noDescription={noDescription}
|
||||
noPrice={noPrice}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
@ -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 {
|
||||
|
@ -14,11 +14,15 @@ import { useUserPreferences } from '@context/UserPreferences'
|
||||
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>
|
||||
{!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}>
|
||||
|
@ -15,6 +15,7 @@ import NetworkName from '@shared/NetworkName'
|
||||
import content from '../../../../content/purgatory.json'
|
||||
import Web3 from 'web3'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import RelatedAssets from '../RelatedAssets'
|
||||
|
||||
export default function AssetContent({
|
||||
asset
|
||||
@ -78,6 +79,7 @@ export default function AssetContent({
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<RelatedAssets />
|
||||
</div>
|
||||
</article>
|
||||
</>
|
||||
|
33
src/components/Asset/RelatedAssets/_utils.ts
Normal file
33
src/components/Asset/RelatedAssets/_utils.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { SortTermOptions } from '../../../@types/aquarius/SearchQuery'
|
||||
|
||||
export function generateQuery(
|
||||
chainIds: number[],
|
||||
nftAddress: string,
|
||||
size: number,
|
||||
tags?: string[],
|
||||
owner?: string
|
||||
) {
|
||||
return {
|
||||
chainIds,
|
||||
esPaginationOptions: {
|
||||
size
|
||||
},
|
||||
nestedQuery: {
|
||||
must_not: {
|
||||
term: { 'nftAddress.keyword': nftAddress }
|
||||
}
|
||||
},
|
||||
filters: [
|
||||
tags && {
|
||||
terms: { 'metadata.tags.keyword': tags }
|
||||
},
|
||||
owner && { term: { 'nft.owner.keyword': owner } }
|
||||
],
|
||||
sort: {
|
||||
'stats.orders': 'desc'
|
||||
},
|
||||
sortOptions: {
|
||||
sortBy: SortTermOptions.Orders
|
||||
} as SortOptions
|
||||
} as BaseQueryParams
|
||||
}
|
8
src/components/Asset/RelatedAssets/index.module.css
Normal file
8
src/components/Asset/RelatedAssets/index.module.css
Normal file
@ -0,0 +1,8 @@
|
||||
.section {
|
||||
margin-top: calc(var(--spacer) * 2);
|
||||
}
|
||||
|
||||
.section > h3 {
|
||||
font-size: var(--font-size-large);
|
||||
color: var(--color-secondary);
|
||||
}
|
67
src/components/Asset/RelatedAssets/index.test.tsx
Normal file
67
src/components/Asset/RelatedAssets/index.test.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import RelatedAssets from '.'
|
||||
import { assets } from '../../../../.jest/__fixtures__/assetsWithAccessDetails'
|
||||
import { queryMetadata } from '../../../@utils/aquarius'
|
||||
// import * as userPreferencesMock from '../../../@context/UserPreferences'
|
||||
|
||||
jest.mock('../../../@utils/aquarius')
|
||||
// jest.mock('../../src/@context/UserPreferences', () => ({
|
||||
// useUserPreferences: () => ({ chainIds: [1, 2, 3] })
|
||||
// }))
|
||||
|
||||
const queryMetadataBaseReturn: PagedAssets = {
|
||||
results: assets,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
totalResults: 10,
|
||||
aggregations: {}
|
||||
}
|
||||
|
||||
describe('Asset/RelatedAssets', () => {
|
||||
beforeAll(() => jest.resetAllMocks())
|
||||
|
||||
it('renders with more than 4 queryMetadata() results', async () => {
|
||||
;(queryMetadata as jest.Mock).mockReturnValue(queryMetadataBaseReturn)
|
||||
render(<RelatedAssets />)
|
||||
await screen.findByText(assets[0].metadata.name)
|
||||
})
|
||||
|
||||
it('renders with 4 queryMetadata() results', async () => {
|
||||
;(queryMetadata as jest.Mock).mockReturnValue({
|
||||
...queryMetadataBaseReturn,
|
||||
results: assets.slice(0, 4),
|
||||
totalResults: 4
|
||||
})
|
||||
render(<RelatedAssets />)
|
||||
await screen.findByText(assets[0].metadata.name)
|
||||
})
|
||||
|
||||
// TODO: figure out how to overwrite already mocked module
|
||||
// it('does nothing when no chainIds selected', async () => {
|
||||
// jest
|
||||
// .spyOn(userPreferencesMock, 'useUserPreferences')
|
||||
// .mockReturnValue({ chainIds: [] } as any)
|
||||
|
||||
// render(<RelatedAssets />)
|
||||
// await screen.findByText('No results found')
|
||||
// })
|
||||
|
||||
it('catches queryMetadata errors', async () => {
|
||||
;(queryMetadata as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('Hello error')
|
||||
})
|
||||
|
||||
// prevent console error from showing up in test log
|
||||
const originalError = console.error
|
||||
console.error = jest.fn()
|
||||
|
||||
try {
|
||||
render(<RelatedAssets />)
|
||||
} catch (error) {
|
||||
expect(error).toEqual({ message: 'Hello error' })
|
||||
}
|
||||
|
||||
console.error = originalError
|
||||
})
|
||||
})
|
88
src/components/Asset/RelatedAssets/index.tsx
Normal file
88
src/components/Asset/RelatedAssets/index.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
||||
import { generateBaseQuery, queryMetadata } from '@utils/aquarius'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import styles from './index.module.css'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import AssetList from '@shared/AssetList'
|
||||
import { generateQuery } from './_utils'
|
||||
|
||||
export default function RelatedAssets(): ReactElement {
|
||||
const { asset } = useAsset()
|
||||
const { chainIds } = useUserPreferences()
|
||||
const newCancelToken = useCancelToken()
|
||||
|
||||
const [relatedAssets, setRelatedAssets] = useState<Asset[]>()
|
||||
const [isLoading, setIsLoading] = useState<boolean>()
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!chainIds?.length ||
|
||||
!asset?.nftAddress ||
|
||||
!asset?.nft ||
|
||||
!asset?.metadata
|
||||
) {
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
async function getAssets() {
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const tagQuery = generateBaseQuery(
|
||||
generateQuery(chainIds, asset.nftAddress, 4, asset.metadata.tags)
|
||||
)
|
||||
const tagResults = (await queryMetadata(tagQuery, newCancelToken()))
|
||||
?.results
|
||||
|
||||
if (tagResults.length === 4) {
|
||||
setRelatedAssets(tagResults)
|
||||
} else {
|
||||
const ownerQuery = generateBaseQuery(
|
||||
generateQuery(
|
||||
chainIds,
|
||||
asset.nftAddress,
|
||||
4 - tagResults.length,
|
||||
null,
|
||||
asset.nft.owner
|
||||
)
|
||||
)
|
||||
|
||||
const ownerResults = (
|
||||
await queryMetadata(ownerQuery, newCancelToken())
|
||||
)?.results
|
||||
|
||||
// combine both results, and filter out duplicates
|
||||
// stolen from: https://stackoverflow.com/a/70326769/733677
|
||||
const bothResults = tagResults.concat(
|
||||
ownerResults.filter(
|
||||
(asset2) => !tagResults.find((asset1) => asset1.id === asset2.id)
|
||||
)
|
||||
)
|
||||
setRelatedAssets(bothResults)
|
||||
}
|
||||
} catch (error) {
|
||||
LoggerInstance.error(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
getAssets()
|
||||
}, [chainIds, asset, newCancelToken])
|
||||
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<h3>Related Assets</h3>
|
||||
<AssetList
|
||||
assets={relatedAssets}
|
||||
showPagination={false}
|
||||
isLoading={isLoading}
|
||||
noDescription
|
||||
noPublisher
|
||||
noPrice
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user