[MM-54819] Convert ./components/external_image/external_image.tsx from Class Component to Function Component (#24941)

Этот коммит содержится в:
Gibson Han
2023-10-30 09:43:06 -07:00
коммит произвёл GitHub
родитель 7480fcfdf7
Коммит af8c9ae0ca
8 изменённых файлов: 140 добавлений и 144 удалений

Просмотреть файл

@@ -23,7 +23,7 @@ describe('ExternalImage', () => {
}; };
test('should render an image', () => { test('should render an image', () => {
const wrapper = shallow<ExternalImage>(<ExternalImage {...baseProps}/>); const wrapper = shallow(<ExternalImage {...baseProps}/>);
expect(baseProps.children).toHaveBeenCalledWith(baseProps.src); expect(baseProps.children).toHaveBeenCalledWith(baseProps.src);
expect(wrapper.find('img').exists()).toBe(true); expect(wrapper.find('img').exists()).toBe(true);
@@ -35,7 +35,7 @@ describe('ExternalImage', () => {
imageMetadata: undefined, imageMetadata: undefined,
}; };
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>); const wrapper = shallow(<ExternalImage {...props}/>);
expect(baseProps.children).toHaveBeenCalledWith(baseProps.src); expect(baseProps.children).toHaveBeenCalledWith(baseProps.src);
expect(wrapper.find('img').exists()).toBe(true); expect(wrapper.find('img').exists()).toBe(true);
@@ -53,7 +53,7 @@ describe('ExternalImage', () => {
src: 'https://example.com/logo.svg', src: 'https://example.com/logo.svg',
}; };
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>); const wrapper = shallow(<ExternalImage {...props}/>);
expect(props.children).toHaveBeenCalledWith(props.src); expect(props.children).toHaveBeenCalledWith(props.src);
expect(wrapper.find('img').exists()).toBe(true); expect(wrapper.find('img').exists()).toBe(true);
@@ -72,7 +72,7 @@ describe('ExternalImage', () => {
src: 'https://example.com/logo.svg', src: 'https://example.com/logo.svg',
}; };
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>); const wrapper = shallow(<ExternalImage {...props}/>);
expect(props.children).toHaveBeenCalledWith(''); expect(props.children).toHaveBeenCalledWith('');
expect(wrapper.find('img').exists()).toBe(true); expect(wrapper.find('img').exists()).toBe(true);
@@ -84,85 +84,9 @@ describe('ExternalImage', () => {
hasImageProxy: true, hasImageProxy: true,
}; };
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>); const wrapper = shallow(<ExternalImage {...props}/>);
expect(props.children).toHaveBeenCalledWith(Client4.getBaseRoute() + '/image?url=' + encodeURIComponent(props.src)); expect(props.children).toHaveBeenCalledWith(Client4.getBaseRoute() + '/image?url=' + encodeURIComponent(props.src));
expect(wrapper.find('img').exists()).toBe(true); expect(wrapper.find('img').exists()).toBe(true);
}); });
describe('isSVGImage', () => {
for (const testCase of [
{
name: 'no metadata, no extension',
src: 'https://example.com/image.png',
imageMetadata: undefined,
expected: false,
},
{
name: 'no metadata, svg extension',
src: 'https://example.com/image.svg',
imageMetadata: undefined,
expected: true,
},
{
name: 'no metadata, svg extension with query parameter',
src: 'https://example.com/image.svg?a=1',
imageMetadata: undefined,
expected: true,
},
{
name: 'no metadata, svg extension with hash',
src: 'https://example.com/image.svg#abc',
imageMetadata: undefined,
expected: true,
},
{
name: 'no metadata, proxied image',
src: 'https://mattermost.example.com/api/v4/image?url=' + encodeURIComponent('https://example.com/image.png'),
imageMetadata: undefined,
expected: false,
},
{
name: 'no metadata, proxied svg image',
src: 'https://mattermost.example.com/api/v4/image?url=' + encodeURIComponent('https://example.com/image.svg'),
imageMetadata: undefined,
expected: true,
},
{
name: 'with metadata, not an SVG',
src: 'https://example.com/image.png',
imageMetadata: {
format: 'png',
frameCount: 40,
width: 100,
height: 200,
},
expected: false,
},
{
name: 'with metadata, SVG',
src: 'https://example.com/image.svg',
imageMetadata: {
format: 'svg',
frameCount: 30,
width: 10,
height: 20,
},
expected: true,
},
]) {
test(testCase.name, () => {
const props = {
...baseProps,
src: testCase.src,
imageMetadata: testCase.imageMetadata,
};
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>);
expect(wrapper.instance().isSVGImage()).toBe(testCase.expected);
});
}
});
}); });

Просмотреть файл

@@ -1,13 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React, {memo} from 'react';
import type {PostImage} from '@mattermost/types/posts'; import type {PostImage} from '@mattermost/types/posts';
import {getImageSrc} from 'utils/post_utils'; import {getImageSrc} from 'utils/post_utils';
interface Props { import {isSVGImage} from './is_svg_image';
type Props = {
children: (src: string) => React.ReactNode; children: (src: string) => React.ReactNode;
enableSVGs: boolean; enableSVGs: boolean;
hasImageProxy: boolean; hasImageProxy: boolean;
@@ -15,29 +17,13 @@ interface Props {
src: string; src: string;
} }
export default class ExternalImage extends React.PureComponent<Props> { const ExternalImage = (props: Props) => {
isSVGImage = () => { const shouldRenderImage = props.enableSVGs || !isSVGImage(props.imageMetadata, props.src);
if (!this.props.imageMetadata) { let src = getImageSrc(props.src, props.hasImageProxy);
// Just check if the string contains an svg extension instead of if it ends with one because it avoids if (!shouldRenderImage) {
// having to deal with query strings and proxied image URLs src = '';
return this.props.src.indexOf('.svg') !== -1;
}
return this.props.imageMetadata.format === 'svg';
};
shouldRenderImage = () => {
// Return true unless the image is an SVG and we have SVG rendering disabled
return this.props.enableSVGs || !this.isSVGImage();
};
render() {
let src = getImageSrc(this.props.src, this.props.hasImageProxy);
if (!this.shouldRenderImage()) {
src = '';
}
return this.props.children(src);
} }
} return (<>{props.children(src)}</>);
};
export default memo(ExternalImage);

Просмотреть файл

@@ -0,0 +1,73 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {isSVGImage} from './is_svg_image';
describe('ExternalIImage isSVGImage', () => {
for (const testCase of [
{
name: 'no metadata, no extension',
src: 'https://example.com/image.png',
imageMetadata: undefined,
expected: false,
},
{
name: 'no metadata, svg extension',
src: 'https://example.com/image.svg',
imageMetadata: undefined,
expected: true,
},
{
name: 'no metadata, svg extension with query parameter',
src: 'https://example.com/image.svg?a=1',
imageMetadata: undefined,
expected: true,
},
{
name: 'no metadata, svg extension with hash',
src: 'https://example.com/image.svg#abc',
imageMetadata: undefined,
expected: true,
},
{
name: 'no metadata, proxied image',
src: 'https://mattermost.example.com/api/v4/image?url=' + encodeURIComponent('https://example.com/image.png'),
imageMetadata: undefined,
expected: false,
},
{
name: 'no metadata, proxied svg image',
src: 'https://mattermost.example.com/api/v4/image?url=' + encodeURIComponent('https://example.com/image.svg'),
imageMetadata: undefined,
expected: true,
},
{
name: 'with metadata, not an SVG',
src: 'https://example.com/image.png',
imageMetadata: {
format: 'png',
frameCount: 40,
width: 100,
height: 200,
},
expected: false,
},
{
name: 'with metadata, SVG',
src: 'https://example.com/image.svg',
imageMetadata: {
format: 'svg',
frameCount: 30,
width: 10,
height: 20,
},
expected: true,
},
]) {
test(testCase.name, () => {
const {imageMetadata, src} = testCase;
expect(isSVGImage(imageMetadata, src)).toBe(testCase.expected);
});
}
});

Просмотреть файл

@@ -0,0 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {PostImage} from '@mattermost/types/posts';
export const isSVGImage = (imageMetadata: PostImage | undefined, src: string) => {
if (!imageMetadata) {
// Just check if the string contains an svg extension instead of if it ends with one because it avoids
// having to deal with query strings and proxied image URLs
return src.indexOf('.svg') !== -1;
}
return imageMetadata.format === 'svg';
};

Просмотреть файл

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/MarkdownImage should match snapshot 1`] = ` exports[`components/MarkdownImage should match snapshot 1`] = `
<Connect(ExternalImage) <Connect(Component)
imageMetadata={ imageMetadata={
Object { Object {
"format": "png", "format": "png",
@@ -13,7 +13,7 @@ exports[`components/MarkdownImage should match snapshot 1`] = `
src="/images/logo.png" src="/images/logo.png"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
`; `;
exports[`components/MarkdownImage should match snapshot for SizeAwareImage dimensions 1`] = ` exports[`components/MarkdownImage should match snapshot for SizeAwareImage dimensions 1`] = `
@@ -40,7 +40,7 @@ exports[`components/MarkdownImage should match snapshot for SizeAwareImage dimen
`; `;
exports[`components/MarkdownImage should match snapshot for broken link 1`] = ` exports[`components/MarkdownImage should match snapshot for broken link 1`] = `
<Connect(ExternalImage) <Connect(Component)
imageMetadata={ imageMetadata={
Object { Object {
"format": "png", "format": "png",
@@ -52,7 +52,7 @@ exports[`components/MarkdownImage should match snapshot for broken link 1`] = `
src="brokenLink" src="brokenLink"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
`; `;
exports[`components/MarkdownImage should provide image src as an alt text for MarkdownImageExpand if image has no own alt text 1`] = ` exports[`components/MarkdownImage should provide image src as an alt text for MarkdownImageExpand if image has no own alt text 1`] = `

Просмотреть файл

@@ -29,12 +29,12 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
key="attachment__author-name" key="attachment__author-name"
location="message_attachment" location="message_attachment"
> >
<Connect(ExternalImage) <Connect(Component)
key="attachment__author-icon" key="attachment__author-icon"
src="author_icon" src="author_icon"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
<span <span
className="attachment__author-name" className="attachment__author-name"
key="attachment__author-name" key="attachment__author-name"
@@ -77,7 +77,7 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
<div <div
className="attachment__image-container" className="attachment__image-container"
> >
<Connect(ExternalImage) <Connect(Component)
imageMetadata={ imageMetadata={
Object { Object {
"height": 200, "height": 200,
@@ -87,16 +87,16 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
src="image_url" src="image_url"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
</div> </div>
<div <div
className="attachment__footer-container" className="attachment__footer-container"
> >
<Connect(ExternalImage) <Connect(Component)
src="footer_icon" src="footer_icon"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
<span> <span>
footer footer
</span> </span>
@@ -121,7 +121,7 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
<div <div
className="attachment__thumb-container" className="attachment__thumb-container"
> >
<Connect(ExternalImage) <Connect(Component)
imageMetadata={ imageMetadata={
Object { Object {
"height": 200, "height": 200,
@@ -131,7 +131,7 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
src="thumb_url" src="thumb_url"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
</div> </div>
<div <div
style={ style={
@@ -175,12 +175,12 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
key="attachment__author-name" key="attachment__author-name"
location="message_attachment" location="message_attachment"
> >
<Connect(ExternalImage) <Connect(Component)
key="attachment__author-icon" key="attachment__author-icon"
src="author_icon" src="author_icon"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
<span <span
className="attachment__author-name" className="attachment__author-name"
key="attachment__author-name" key="attachment__author-name"
@@ -223,7 +223,7 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
<div <div
className="attachment__image-container" className="attachment__image-container"
> >
<Connect(ExternalImage) <Connect(Component)
imageMetadata={ imageMetadata={
Object { Object {
"height": 200, "height": 200,
@@ -233,16 +233,16 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
src="image_url" src="image_url"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
</div> </div>
<div <div
className="attachment__footer-container" className="attachment__footer-container"
> >
<Connect(ExternalImage) <Connect(Component)
src="footer_icon" src="footer_icon"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
<span> <span>
footer footer
</span> </span>
@@ -251,7 +251,7 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
<div <div
className="attachment__thumb-container" className="attachment__thumb-container"
> >
<Connect(ExternalImage) <Connect(Component)
imageMetadata={ imageMetadata={
Object { Object {
"height": 200, "height": 200,
@@ -261,7 +261,7 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
src="thumb_url" src="thumb_url"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
</div> </div>
<div <div
style={ style={
@@ -317,12 +317,12 @@ exports[`components/post_view/MessageAttachment should match snapshot when no fo
key="attachment__author-name" key="attachment__author-name"
location="message_attachment" location="message_attachment"
> >
<Connect(ExternalImage) <Connect(Component)
key="attachment__author-icon" key="attachment__author-icon"
src="author_icon" src="author_icon"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
<span <span
className="attachment__author-name" className="attachment__author-name"
key="attachment__author-name" key="attachment__author-name"
@@ -365,7 +365,7 @@ exports[`components/post_view/MessageAttachment should match snapshot when no fo
<div <div
className="attachment__image-container" className="attachment__image-container"
> >
<Connect(ExternalImage) <Connect(Component)
imageMetadata={ imageMetadata={
Object { Object {
"height": 200, "height": 200,
@@ -375,13 +375,13 @@ exports[`components/post_view/MessageAttachment should match snapshot when no fo
src="image_url" src="image_url"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
</div> </div>
</div> </div>
<div <div
className="attachment__thumb-container" className="attachment__thumb-container"
> >
<Connect(ExternalImage) <Connect(Component)
imageMetadata={ imageMetadata={
Object { Object {
"height": 200, "height": 200,
@@ -391,7 +391,7 @@ exports[`components/post_view/MessageAttachment should match snapshot when no fo
src="thumb_url" src="thumb_url"
> >
<Component /> <Component />
</Connect(ExternalImage)> </Connect(Component)>
</div> </div>
<div <div
style={ style={

Просмотреть файл

@@ -182,7 +182,7 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
<AutoHeightSwitcher <AutoHeightSwitcher
showSlot={1} showSlot={1}
slot1={ slot1={
<Memo(Connect(ExternalImage)) <Memo(Connect(Component))
imageMetadata={ imageMetadata={
Object { Object {
"format": "png", "format": "png",
@@ -197,7 +197,7 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
src="http://localhost:8065/api/v4/image?url=http%3A%2F%2Fmattermo…t.com%2Fwp-content%2Fuploads%2F2021%2F09%2FHomepage%402x.png" src="http://localhost:8065/api/v4/image?url=http%3A%2F%2Fmattermo…t.com%2Fwp-content%2Fuploads%2F2021%2F09%2FHomepage%402x.png"
> >
[Function] [Function]
</Memo(Connect(ExternalImage))> </Memo(Connect(Component))>
} }
slot2={ slot2={
<button <button
@@ -240,7 +240,7 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
} }
> >
<div> <div>
<Connect(ExternalImage) <Connect(Component)
imageMetadata={ imageMetadata={
Object { Object {
"format": "png", "format": "png",
@@ -254,7 +254,7 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
} }
src="http://localhost:8065/api/v4/image?url=http%3A%2F%2Fmattermo…t.com%2Fwp-content%2Fuploads%2F2021%2F09%2FHomepage%402x.png" src="http://localhost:8065/api/v4/image?url=http%3A%2F%2Fmattermo…t.com%2Fwp-content%2Fuploads%2F2021%2F09%2FHomepage%402x.png"
> >
<ExternalImage <Memo(ExternalImage)
dispatch={[Function]} dispatch={[Function]}
enableSVGs={true} enableSVGs={true}
hasImageProxy={true} hasImageProxy={true}
@@ -299,8 +299,8 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
src="/api/v4/image?url=http%3A%2F%2Flocalhost%3A8065%2Fapi%2Fv4%2Fimage%3Furl%3Dhttp%253A%252F%252Fmattermo%E2%80%A6t.com%252Fwp-content%252Fuploads%252F2021%252F09%252FHomepage%25402x.png" src="/api/v4/image?url=http%3A%2F%2Flocalhost%3A8065%2Fapi%2Fv4%2Fimage%3Furl%3Dhttp%253A%252F%252Fmattermo%E2%80%A6t.com%252Fwp-content%252Fuploads%252F2021%252F09%252FHomepage%25402x.png"
/> />
</figure> </figure>
</ExternalImage> </Memo(ExternalImage)>
</Connect(ExternalImage)> </Connect(Component)>
</div> </div>
</div> </div>
</Transition> </Transition>

Просмотреть файл

@@ -70,10 +70,10 @@ exports[`YoutubeVideo should match init snapshot 1`] = `
<div <div
className="video-thumbnail__container" className="video-thumbnail__container"
> >
<Connect(ExternalImage) <Connect(Component)
src="linkForThumbnail" src="linkForThumbnail"
> >
<ExternalImage <Memo(ExternalImage)
dispatch={[Function]} dispatch={[Function]}
enableSVGs={false} enableSVGs={false}
hasImageProxy={false} hasImageProxy={false}
@@ -84,8 +84,8 @@ exports[`YoutubeVideo should match init snapshot 1`] = `
className="video-thumbnail" className="video-thumbnail"
src="linkForThumbnail" src="linkForThumbnail"
/> />
</ExternalImage> </Memo(ExternalImage)>
</Connect(ExternalImage)> </Connect(Component)>
<div <div
className="block" className="block"
> >