diff --git a/src/common/components/product-info/__stories__/index.stories.tsx b/src/common/components/product-info/__stories__/index.stories.tsx index dd8ab080..72c862a2 100644 --- a/src/common/components/product-info/__stories__/index.stories.tsx +++ b/src/common/components/product-info/__stories__/index.stories.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { action } from '@storybook/addon-actions'; import { ProductInfo, Parts } from '..'; import { Badge, BadgeProps } from '../../badge'; @@ -6,6 +6,7 @@ import { Stepper } from '@sima-land/ui-nucleons/stepper'; import FavoriteSVG from '@sima-land/ui-quarks/icons/24x24/Stroked/favorite'; import QuickView2SVG from '@sima-land/ui-quarks/icons/24x24/Stroked/quick-view-2'; import Camera2SVG from '@sima-land/ui-quarks/icons/24x24/Stroked/camera-2'; +import on from '@sima-land/ui-nucleons/helpers/on'; export default { title: 'common/ProductInfo', @@ -29,7 +30,7 @@ const badges: BadgeProps[] = [ const data = { name: 'Ножницы портновские, с прорезиненной ручкой, 20 см, цвет чёрный/серый', - imageSrc: 'https://picsum.photos/240/360', + imageSrc: 'https://loremflickr.com/240/360', url: 'https://www.sima-land.ru', price: 99876543.21, oldPrice: 99987654.32, @@ -500,3 +501,46 @@ export const Adult = () => ( ); Adult.storyName = 'Товар для взрослых'; + +export function TestBrokenImage() { + const brokenSrc = 'https://kasjbgakjbsg.asgkjabsghj'; + const [imageSrc, setImageSrc] = useState(data.imageSrc); + + useEffect(() => { + const off = on(window, 'keydown', e => { + if (e.code === 'KeyR') { + setImageSrc(src => (src === brokenSrc ? data.imageSrc : brokenSrc)); + } + }); + + return off; + }, []); + + return ( +
+ + + + + + + + + {data.name} + +

Нажмите R на клавиатуре чтобы переключить картинку на битую и обратно

+
+ ); +} diff --git a/src/common/components/product-info/__test__/index.test.tsx b/src/common/components/product-info/__test__/index.test.tsx index d6a51165..28ddeacc 100644 --- a/src/common/components/product-info/__test__/index.test.tsx +++ b/src/common/components/product-info/__test__/index.test.tsx @@ -383,4 +383,51 @@ describe('ProductInfo', () => { expect(getByTestId('product-info:prices').textContent).toContain('Test reason'); }); }); + + it('should render stub for broken image', () => { + const { container, getByTestId } = render( + + + + + + + + {data.name} + , + ); + + expect(container.querySelectorAll('.broken-icon')).toHaveLength(0); + fireEvent.error(getByTestId('product-info:image')); + expect(container.querySelectorAll('.broken-icon')).toHaveLength(1); + }); + + it('should render stub for broken adult image', () => { + const { container, getByTestId } = render( + + + + + + + + {data.name} + , + ); + + expect(container.querySelectorAll('.broken-icon')).toHaveLength(0); + fireEvent.error(getByTestId('product-info:adult-image')); + expect(container.querySelectorAll('.broken-icon')).toHaveLength(0); + expect(getByTestId('product-info:adult-image').classList.contains('broken')).toBe(true); + }); }); diff --git a/src/common/components/product-info/parts.tsx b/src/common/components/product-info/parts.tsx index 9627b0fe..cd2ab15d 100644 --- a/src/common/components/product-info/parts.tsx +++ b/src/common/components/product-info/parts.tsx @@ -1,4 +1,4 @@ -import React, { createContext, ReactNode, useContext } from 'react'; +import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react'; import { Link, LinkProps } from '@sima-land/ui-nucleons/link'; import { HintProps } from '@sima-land/ui-nucleons/hint-deprecated'; import { Price } from '@sima-land/ui-nucleons/price'; @@ -9,6 +9,7 @@ import { BadgeList, BadgeListProps } from '../badge-list'; import { ProductInfoContext } from './utils'; import { RatingCounter, RatingCounterProps } from '../rating-counter'; import AdultSVG from '../../icons/eighteen-plus.svg'; +import BrokenSVG from '../../icons/image-broken.svg'; import classnames from 'classnames/bind'; import styles from './product-info.module.scss'; @@ -86,15 +87,19 @@ const ImageButton = ({ const Image = ({ src, alt, href, onClick, children, opacity }: ImageProps) => { const { restriction } = useContext(ProductInfoContext); const defaultOpacity = restriction ? 0.4 : undefined; + const [broken, setBroken] = useState(false); + + useEffect(() => setBroken(false), [src]); return ( {restriction === 'adult' ? ( <> setBroken(true)} alt={alt} src={src} - className={cx('image', 'adult')} + className={cx('image', 'adult', { broken })} data-testid='product-info:adult-image' /> @@ -108,12 +113,14 @@ const Image = ({ src, alt, href, onClick, children, opacity }: ImageProps) => { data-testid='product-info:image-link' > setBroken(true)} alt={alt} src={src} - className={cx('image')} + className={cx('image', { broken })} style={{ opacity: typeof opacity === 'number' ? opacity : defaultOpacity }} data-testid='product-info:image' /> + {broken && } {children &&
{children}
} diff --git a/src/common/components/product-info/product-info.module.scss b/src/common/components/product-info/product-info.module.scss index 76b1f7ec..ddff2947 100644 --- a/src/common/components/product-info/product-info.module.scss +++ b/src/common/components/product-info/product-info.module.scss @@ -31,6 +31,9 @@ object-fit: contain; width: 100%; height: 100%; + &.broken { + display: none; + } &.adult { position: absolute; top: 0; @@ -51,6 +54,12 @@ fill: colors.$basic-gray38; } +.broken-icon { + position: absolute; + width: 100%; + height: 100%; +} + .image-buttons { position: absolute; top: 0; diff --git a/src/common/icons/image-broken.svg b/src/common/icons/image-broken.svg new file mode 100644 index 00000000..5d4e72dc --- /dev/null +++ b/src/common/icons/image-broken.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/desktop/components/product-carousel/__test__/__snapshots__/index.test.tsx.snap b/src/desktop/components/product-carousel/__test__/__snapshots__/index.test.tsx.snap index 607875ca..9ab45768 100644 --- a/src/desktop/components/product-carousel/__test__/__snapshots__/index.test.tsx.snap +++ b/src/desktop/components/product-carousel/__test__/__snapshots__/index.test.tsx.snap @@ -1217,6 +1217,7 @@ exports[` should set size depend on media query 1`] = ` should set size depend on media query 1`] = ` should set size depend on media query 1`] = ` should set size depend on media query 1`] = ` should set size depend on media query 1`] = ` should set size depend on media query 1`] = ` should set size depend on media query 1`] = ` should set size depend on media query 1`] = ` should set size depend on media query 1`] = ` should set size depend on media query 1`] = ` should set size depend on media query 2`] = ` should set size depend on media query 2`] = ` should set size depend on media query 2`] = ` should set size depend on media query 2`] = ` should set size depend on media query 2`] = ` should set size depend on media query 2`] = ` should set size depend on media query 2`] = ` should set size depend on media query 2`] = ` should set size depend on media query 2`] = ` should set size depend on media query 2`] = `