Commit dfc01750 authored by tom goriunov's avatar tom goriunov Committed by GitHub

bugfix: media is not shown for NFT (#1095)

* combine imageUrl and animationUrl prop for NftMedia

* add lazy load to NftMedia

* refactor image and video components

* add support for NFT HTML media

* move pw image mocks to separate folder

* tests

* fix loading state
parent c58a3fdf
...@@ -105,7 +105,7 @@ export function app(): CspDev.DirectiveDescriptor { ...@@ -105,7 +105,7 @@ export function app(): CspDev.DirectiveDescriptor {
], ],
'frame-src': [ 'frame-src': [
// improve: allow only frames from marketplace config // could be a marketplace app or NFT media (html-page)
'*', '*',
], ],
......
...@@ -17,7 +17,17 @@ export default async function mediaTypeHandler(req: NextApiRequest, res: NextApi ...@@ -17,7 +17,17 @@ export default async function mediaTypeHandler(req: NextApiRequest, res: NextApi
} }
const contentType = response.headers.get('content-type'); const contentType = response.headers.get('content-type');
const mediaType = contentType?.startsWith('video') ? 'video' : 'image'; const mediaType = (() => {
if (contentType?.startsWith('video')) {
return 'video';
}
if (contentType === 'text/html') {
return 'html';
}
return 'image';
})();
res.status(200).json({ type: mediaType }); res.status(200).json({ type: mediaType });
} catch (error) { } catch (error) {
res.status(200).json({ type: undefined }); res.status(200).json({ type: undefined });
......
<html>
<head>
<style>
body {
background-color: lightpink;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
}
</style>
</head>
<body>
this is HTML page
</body>
</html>
\ No newline at end of file
...@@ -29,7 +29,7 @@ const test = base.extend({ ...@@ -29,7 +29,7 @@ const test = base.extend({
await page.route(ASSET_URL, (route) => { await page.route(ASSET_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
await page.route(ADDRESS_API_URL, (route) => route.fulfill({ await page.route(ADDRESS_API_URL, (route) => route.fulfill({
...@@ -141,7 +141,7 @@ base('long values', async({ mount, page }) => { ...@@ -141,7 +141,7 @@ base('long values', async({ mount, page }) => {
await page.route(ASSET_URL, (route) => { await page.route(ASSET_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
await page.route(ADDRESS_API_URL, (route) => route.fulfill({ await page.route(ADDRESS_API_URL, (route) => route.fulfill({
......
...@@ -30,8 +30,7 @@ const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance, isLo ...@@ -30,8 +30,7 @@ const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance, isLo
<LinkOverlay href={ isLoading ? undefined : tokenLink }> <LinkOverlay href={ isLoading ? undefined : tokenLink }>
<NftMedia <NftMedia
mb="18px" mb="18px"
imageUrl={ tokenInstance?.image_url || null } url={ tokenInstance?.animation_url || tokenInstance?.image_url || null }
animationUrl={ tokenInstance?.animation_url || null }
isLoading={ isLoading } isLoading={ isLoading }
/> />
</LinkOverlay> </LinkOverlay>
......
...@@ -35,7 +35,7 @@ test.beforeEach(async({ page }) => { ...@@ -35,7 +35,7 @@ test.beforeEach(async({ page }) => {
await page.route(textAdMock.duck.ad.thumbnail, (route) => { await page.route(textAdMock.duck.ad.thumbnail, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
}); });
......
...@@ -80,7 +80,7 @@ test.describe('custom hero plate background', () => { ...@@ -80,7 +80,7 @@ test.describe('custom hero plate background', () => {
await page.route(IMAGE_URL, (route) => { await page.route(IMAGE_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/giant_duck_long.jpg', path: './playwright/mocks/image_long.jpg',
}); });
}); });
......
...@@ -31,7 +31,7 @@ test('search by name +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -31,7 +31,7 @@ test('search by name +@mobile +@dark-mode', async({ mount, page }) => {
await page.route(searchMock.token1.icon_url as string, (route) => { await page.route(searchMock.token1.icon_url as string, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
...@@ -204,13 +204,13 @@ test.describe('with apps', () => { ...@@ -204,13 +204,13 @@ test.describe('with apps', () => {
await page.route(appsMock[0].logo, (route) => { await page.route(appsMock[0].logo, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
await page.route(appsMock[1].logo as string, (route) => { await page.route(appsMock[1].logo as string, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
......
...@@ -80,7 +80,7 @@ test('with verified info', async({ mount, page, createSocket }) => { ...@@ -80,7 +80,7 @@ test('with verified info', async({ mount, page, createSocket }) => {
await page.route(tokenInfo.icon_url as string, (route) => { await page.route(tokenInfo.icon_url as string, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
...@@ -131,7 +131,7 @@ test.describe('mobile', () => { ...@@ -131,7 +131,7 @@ test.describe('mobile', () => {
await page.route(tokenInfo.icon_url as string, (route) => { await page.route(tokenInfo.icon_url as string, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
......
...@@ -14,7 +14,7 @@ test.beforeEach(async({ context }) => { ...@@ -14,7 +14,7 @@ test.beforeEach(async({ context }) => {
await context.route(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, (route) => { await context.route(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
}); });
......
...@@ -20,7 +20,7 @@ test.beforeEach(async({ page }) => { ...@@ -20,7 +20,7 @@ test.beforeEach(async({ page }) => {
await page.route(textAdMock.duck.ad.thumbnail, (route) => { await page.route(textAdMock.duck.ad.thumbnail, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
}); });
......
...@@ -16,13 +16,13 @@ test.beforeEach(async({ page }) => { ...@@ -16,13 +16,13 @@ test.beforeEach(async({ page }) => {
await page.route(textAdMock.duck.ad.thumbnail, (route) => { await page.route(textAdMock.duck.ad.thumbnail, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
await page.route('https://example.com/logo.png', (route) => { await page.route('https://example.com/logo.png', (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
}); });
......
...@@ -71,7 +71,7 @@ test('with logo and long symbol', async({ mount, page }) => { ...@@ -71,7 +71,7 @@ test('with logo and long symbol', async({ mount, page }) => {
await page.route(API_URL, (route) => { await page.route(API_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
......
import { Icon, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import nftIcon from 'icons/nft_shield.svg';
const NftFallback = () => {
return (
<Icon
as={ nftIcon }
p="50px"
color={ useColorModeValue('blackAlpha.500', 'whiteAlpha.500') }
/>
);
};
export default NftFallback;
import { chakra } from '@chakra-ui/react';
import React from 'react';
interface Props {
src: string;
onLoad: () => void;
onError: () => void;
}
const NftHtml = ({ src, onLoad, onError }: Props) => {
return (
<chakra.iframe
src={ src }
sandbox="allow-scripts"
onLoad={ onLoad }
onError={ onError }
/>
);
};
export default NftHtml;
import type { ResponsiveValue } from '@chakra-ui/react'; import { Image } from '@chakra-ui/react';
import { Box, AspectRatio, chakra, Icon, Image, shouldForwardProp, Skeleton, useColorModeValue } from '@chakra-ui/react';
import type { Property } from 'csstype';
import React from 'react'; import React from 'react';
import nftIcon from 'icons/nft_shield.svg';
interface Props { interface Props {
url: string | null; url: string;
className?: string; onLoad: () => void;
fallbackPadding?: string; onError: () => void;
objectFit: ResponsiveValue<Property.ObjectFit>;
}
interface FallbackProps {
className?: string;
padding?: string;
} }
const Fallback = ({ className, padding }: FallbackProps) => { const NftImage = ({ url, onLoad, onError }: Props) => {
return ( return (
<Icon
className={ className }
as={ nftIcon }
p={ padding ?? '50px' }
color={ useColorModeValue('blackAlpha.500', 'whiteAlpha.500') }
/>
);
};
const NftImage = ({ url, className, fallbackPadding, objectFit }: Props) => {
const [ isLoading, setIsLoading ] = React.useState(true);
const [ isError, setIsError ] = React.useState(false);
const handleLoad = React.useCallback(() => {
setIsLoading(false);
}, []);
const handleLoadError = React.useCallback(() => {
setIsLoading(false);
setIsError(true);
}, []);
const _objectFit = objectFit || 'contain';
const content = (() => {
// as of ChakraUI v2.5.3
// fallback prop of Image component doesn't work well with loading prop lazy strategy
// so we have to render fallback and loader manually
if (isError || !url) {
return <Fallback className={ className } padding={ fallbackPadding }/>;
}
return (
<Box>
{ isLoading && <Skeleton position="absolute" left={ 0 } top={ 0 } w="100%" h="100%" zIndex="1"/> }
<Image <Image
w="100%" w="100%"
h="100%" h="100%"
objectFit={ _objectFit }
src={ url } src={ url }
opacity={ isLoading ? 0 : 1 }
alt="Token instance image" alt="Token instance image"
onError={ handleLoadError } onError={ onError }
onLoad={ handleLoad } onLoad={ onLoad }
loading={ url ? 'lazy' : undefined }
/> />
</Box>
);
})();
return (
<AspectRatio
className={ className }
ratio={ 1 / 1 }
bgColor={ useColorModeValue('blackAlpha.50', 'whiteAlpha.50') }
overflow="hidden"
borderRadius="md"
sx={{
'&>img': {
objectFit: _objectFit,
},
}}
>
{ content }
</AspectRatio>
); );
}; };
const NftImageChakra = chakra(NftImage, { export default NftImage;
shouldForwardProp: (prop) => {
const isChakraProp = !shouldForwardProp(prop);
if (isChakraProp && prop !== 'objectFit') {
return false;
}
return true;
},
});
export default NftImageChakra;
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
import NftMedia from './NftMedia';
test.use({ viewport: { width: 250, height: 250 } });
test('no url +@dark-mode', async({ mount }) => {
const component = await mount(
<TestApp>
<NftMedia url={ null }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('image +@dark-mode', async({ mount, page }) => {
const MEDIA_URL = 'https://localhost:3000/my-image.jpg';
await page.route(MEDIA_URL, (route) => {
return route.fulfill({
status: 200,
path: './playwright/mocks/image_long.jpg',
});
});
const component = await mount(
<TestApp>
<NftMedia url={ MEDIA_URL }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('page', async({ mount, page }) => {
const MEDIA_URL = 'https://localhost:3000/page.html';
const MEDIA_TYPE_API_URL = `/node-api/media-type?url=${ encodeURIComponent(MEDIA_URL) }`;
await page.route(MEDIA_URL, (route) => {
return route.fulfill({
status: 200,
path: './playwright/mocks/page.html',
});
});
await page.route(MEDIA_TYPE_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ type: 'html' }),
}));
const component = await mount(
<TestApp>
<NftMedia url={ MEDIA_URL }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
import { AspectRatio, chakra, Skeleton } from '@chakra-ui/react'; import { AspectRatio, chakra, Skeleton, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { useInView } from 'react-intersection-observer';
import type { StaticRoute } from 'nextjs-routes'; import type { StaticRoute } from 'nextjs-routes';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import NftFallback from './NftFallback';
import NftHtml from './NftHtml';
import NftImage from './NftImage'; import NftImage from './NftImage';
import NftVideo from './NftVideo'; import NftVideo from './NftVideo';
import type { MediaType } from './utils'; import type { MediaType } from './utils';
import { getPreliminaryMediaType } from './utils'; import { getPreliminaryMediaType } from './utils';
interface Props { interface Props {
imageUrl: string | null; url: string | null;
animationUrl: string | null;
className?: string; className?: string;
isLoading?: boolean; isLoading?: boolean;
} }
const NftMedia = ({ imageUrl, animationUrl, className, isLoading }: Props) => { const NftMedia = ({ url, className, isLoading }: Props) => {
const [ type, setType ] = React.useState<MediaType | undefined>(!animationUrl ? 'image' : undefined); const [ type, setType ] = React.useState<MediaType | undefined>();
const [ isMediaLoading, setIsMediaLoading ] = React.useState(Boolean(url));
const [ isLoadingError, setIsLoadingError ] = React.useState(false);
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const fetch = useFetch(); const fetch = useFetch();
const { ref, inView } = useInView({ triggerOnce: true });
React.useEffect(() => { React.useEffect(() => {
if (!animationUrl || isLoading) { if (!url || isLoading || !inView) {
return; return;
} }
// media could be either gif or video // media could be either image, gif or video
// so we pre-fetch the resources in order to get its content type // so we pre-fetch the resources in order to get its content type
// have to do it via Node.js due to strict CSP for connect-src // have to do it via Node.js due to strict CSP for connect-src
// but in order not to abuse our server firstly we check file url extension // but in order not to abuse our server firstly we check file url extension
// and if it is valid we will trust it and display corresponding media component // and if it is valid we will trust it and display corresponding media component
const preliminaryType = getPreliminaryMediaType(animationUrl); const preliminaryType = getPreliminaryMediaType(url);
if (preliminaryType) { if (preliminaryType) {
setType(preliminaryType); setType(preliminaryType);
return; return;
} }
const url = route({ pathname: '/node-api/media-type' as StaticRoute<'/api/media-type'>['pathname'], query: { url: animationUrl } }); const mediaTypeResourceUrl = route({ pathname: '/node-api/media-type' as StaticRoute<'/api/media-type'>['pathname'], query: { url } });
fetch(url) fetch(mediaTypeResourceUrl)
.then((_data) => { .then((_data) => {
const data = _data as { type: MediaType | undefined }; const data = _data as { type: MediaType | undefined };
setType(data.type || 'image'); setType(data.type || 'image');
...@@ -50,26 +58,55 @@ const NftMedia = ({ imageUrl, animationUrl, className, isLoading }: Props) => { ...@@ -50,26 +58,55 @@ const NftMedia = ({ imageUrl, animationUrl, className, isLoading }: Props) => {
setType('image'); setType('image');
}); });
}, [ animationUrl, isLoading, fetch ]); }, [ url, isLoading, fetch, inView ]);
const handleMediaLoaded = React.useCallback(() => {
setIsMediaLoading(false);
}, []);
const handleMediaLoadError = React.useCallback(() => {
setIsMediaLoading(false);
setIsLoadingError(true);
}, []);
const content = (() => {
if (!url || isLoadingError) {
return <NftFallback/>;
}
switch (type) {
case 'video':
return <NftVideo src={ url } onLoad={ handleMediaLoaded } onError={ handleMediaLoadError }/>;
case 'html':
return <NftHtml src={ url } onLoad={ handleMediaLoaded } onError={ handleMediaLoadError }/>;
case 'image':
return <NftImage url={ url } onLoad={ handleMediaLoaded } onError={ handleMediaLoadError }/>;
default:
return null;
}
})();
if (!type || isLoading) {
return ( return (
<AspectRatio <AspectRatio
ref={ ref }
className={ className } className={ className }
bgColor={ isLoading || isMediaLoading ? 'transparent' : bgColor }
ratio={ 1 / 1 } ratio={ 1 / 1 }
overflow="hidden" overflow="hidden"
borderRadius="md" borderRadius="md"
objectFit="contain"
sx={{
'&>img, &>video': {
objectFit: 'contain',
},
}}
> >
<Skeleton/> <>
{ content }
{ isMediaLoading && <Skeleton position="absolute" left={ 0 } top={ 0 } w="100%" h="100%" zIndex="1"/> }
</>
</AspectRatio> </AspectRatio>
); );
}
if (animationUrl && type === 'video') {
return <NftVideo className={ className } src={ animationUrl }/>;
}
return <NftImage className={ className } url={ animationUrl || imageUrl }/>;
}; };
export default chakra(NftMedia); export default chakra(NftMedia);
import { AspectRatio, Skeleton, chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
interface Props { interface Props {
className?: string;
src: string; src: string;
onLoad: () => void;
onError: () => void;
} }
const NftVideo = ({ className, src }: Props) => { const NftVideo = ({ src, onLoad, onError }: Props) => {
const [ isLoading, setIsLoading ] = React.useState(true);
const handleCanPlay = React.useCallback(() => {
setIsLoading(false);
}, []);
return ( return (
<AspectRatio
className={ className }
ratio={ 1 / 1 }
overflow="hidden"
borderRadius="md"
>
<>
<chakra.video <chakra.video
src={ src } src={ src }
autoPlay autoPlay
...@@ -28,12 +16,10 @@ const NftVideo = ({ className, src }: Props) => { ...@@ -28,12 +16,10 @@ const NftVideo = ({ className, src }: Props) => {
loop loop
muted muted
playsInline playsInline
onCanPlayThrough={ handleCanPlay } onCanPlayThrough={ onLoad }
onError={ onError }
borderRadius="md" borderRadius="md"
/> />
{ isLoading && <Skeleton position="absolute" w="100%" h="100%" left={ 0 } top={ 0 }/> }
</>
</AspectRatio>
); );
}; };
......
export type MediaType = 'image' | 'video'; export type MediaType = 'image' | 'video' | 'html';
const IMAGE_EXTENSIONS = [ const IMAGE_EXTENSIONS = [
'.jpg', 'jpeg', '.jpg', 'jpeg',
......
...@@ -37,7 +37,7 @@ test('base view', async({ mount, page }) => { ...@@ -37,7 +37,7 @@ test('base view', async({ mount, page }) => {
await page.route(LOGO_URL, (route) => { await page.route(LOGO_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
...@@ -67,7 +67,7 @@ test.describe('dark mode', () => { ...@@ -67,7 +67,7 @@ test.describe('dark mode', () => {
await page.route(LOGO_URL, (route) => { await page.route(LOGO_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
......
...@@ -59,13 +59,13 @@ base.describe('custom logo', () => { ...@@ -59,13 +59,13 @@ base.describe('custom logo', () => {
await page.route(LOGO_URL, (route) => { await page.route(LOGO_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/network-logo.svg', path: './playwright/mocks/network-logo.svg',
}); });
}); });
await page.route(ICON_URL, (route) => { await page.route(ICON_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/duck.svg', path: './playwright/mocks/image_svg.svg',
}); });
}); });
...@@ -108,13 +108,13 @@ base.describe('custom logo with dark option -@default +@dark-mode', () => { ...@@ -108,13 +108,13 @@ base.describe('custom logo with dark option -@default +@dark-mode', () => {
await page.route(LOGO_URL, (route) => { await page.route(LOGO_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/giant_duck_long.jpg', path: './playwright/mocks/image_long.jpg',
}); });
}); });
await page.route(ICON_URL, (route) => { await page.route(ICON_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
......
...@@ -23,7 +23,7 @@ extendedTest('base view +@dark-mode', async({ mount, page }) => { ...@@ -23,7 +23,7 @@ extendedTest('base view +@dark-mode', async({ mount, page }) => {
await page.route(LOGO_URL, (route) => { await page.route(LOGO_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
await page.route(FEATURED_NETWORKS_URL, (route) => { await page.route(FEATURED_NETWORKS_URL, (route) => {
......
...@@ -43,7 +43,7 @@ test.describe('auth', () => { ...@@ -43,7 +43,7 @@ test.describe('auth', () => {
await page.route(profileMock.base.avatar, (route) => { await page.route(profileMock.base.avatar, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
......
...@@ -45,7 +45,7 @@ test.describe('auth', () => { ...@@ -45,7 +45,7 @@ test.describe('auth', () => {
await page.route(profileMock.base.avatar, (route) => { await page.route(profileMock.base.avatar, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
......
...@@ -18,13 +18,13 @@ test.beforeEach(async({ page }) => { ...@@ -18,13 +18,13 @@ test.beforeEach(async({ page }) => {
await page.route(textAdMock.duck.ad.thumbnail, (route) => { await page.route(textAdMock.duck.ad.thumbnail, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
await page.route(searchMock.token1.icon_url as string, (route) => { await page.route(searchMock.token1.icon_url as string, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
}); });
...@@ -306,13 +306,13 @@ test.describe('with apps', () => { ...@@ -306,13 +306,13 @@ test.describe('with apps', () => {
await page.route(appsMock[0].logo, (route) => { await page.route(appsMock[0].logo, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
await page.route(appsMock[1].logo as string, (route) => { await page.route(appsMock[1].logo as string, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_s.jpg', path: './playwright/mocks/image_s.jpg',
}); });
}); });
......
...@@ -18,8 +18,7 @@ const NFTItem = ({ item, isLoading }: Props) => { ...@@ -18,8 +18,7 @@ const NFTItem = ({ item, isLoading }: Props) => {
const mediaElement = ( const mediaElement = (
<NftMedia <NftMedia
mb="18px" mb="18px"
imageUrl={ item.image_url } url={ item.animation_url || item.image_url }
animationUrl={ item.animation_url }
isLoading={ isLoading } isLoading={ isLoading }
/> />
); );
......
...@@ -13,7 +13,7 @@ test('base view +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -13,7 +13,7 @@ test('base view +@mobile +@dark-mode', async({ mount, page }) => {
await page.route(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, (route) => { await page.route(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_md.jpg', path: './playwright/mocks/image_md.jpg',
}); });
}); });
...@@ -41,7 +41,7 @@ test('status IN_PROCESS', async({ mount, page }) => { ...@@ -41,7 +41,7 @@ test('status IN_PROCESS', async({ mount, page }) => {
await page.route(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, (route) => { await page.route(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
path: './playwright/image_md.jpg', path: './playwright/mocks/image_md.jpg',
}); });
}); });
......
...@@ -82,8 +82,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { ...@@ -82,8 +82,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => {
<TokenInstanceTransfersCount hash={ isLoading ? '' : data.token.address } id={ isLoading ? '' : data.id } onClick={ handleCounterItemClick }/> <TokenInstanceTransfersCount hash={ isLoading ? '' : data.token.address } id={ isLoading ? '' : data.id } onClick={ handleCounterItemClick }/>
</Grid> </Grid>
<NftMedia <NftMedia
imageUrl={ data.image_url } url={ data.animation_url || data.image_url }
animationUrl={ data.animation_url }
w="250px" w="250px"
flexShrink={ 0 } flexShrink={ 0 }
alignSelf={{ base: 'center', lg: 'flex-start' }} alignSelf={{ base: 'center', lg: 'flex-start' }}
......
...@@ -10998,6 +10998,11 @@ react-inspector@^6.0.1: ...@@ -10998,6 +10998,11 @@ react-inspector@^6.0.1:
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.1.tgz#1a37f0165d9df81ee804d63259eaaeabe841287d" resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.1.tgz#1a37f0165d9df81ee804d63259eaaeabe841287d"
integrity sha512-cxKSeFTf7jpSSVddm66sKdolG90qURAX3g1roTeaN6x0YEbtWc8JpmFN9+yIqLNH2uEkYerWLtJZIXRIFuBKrg== integrity sha512-cxKSeFTf7jpSSVddm66sKdolG90qURAX3g1roTeaN6x0YEbtWc8JpmFN9+yIqLNH2uEkYerWLtJZIXRIFuBKrg==
react-intersection-observer@^9.5.2:
version "9.5.2"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.5.2.tgz#f68363a1ff292323c0808201b58134307a1626d0"
integrity sha512-EmoV66/yvksJcGa1rdW0nDNc4I1RifDWkT50gXSFnPLYQ4xUptuDD4V7k+Rj1OgVAlww628KLGcxPXFlOkkU/Q==
react-is@^16.13.1, react-is@^16.7.0: react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment