[MM-62570] PluginLinkTooltip throws warning that class is invalid dom property (#29858)

Этот коммит содержится в:
M-ZubairAhmed
2025-01-18 19:51:56 +05:30
коммит произвёл GitHub
родитель 7322fa1a3f
Коммит f3d1bc0731
3 изменённых файлов: 90 добавлений и 5 удалений

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

@@ -147,7 +147,7 @@ exports[`messageHtmlToComponent link with enabled a tooltip plugin 1`] = `
<PluginLinkTooltip <PluginLinkTooltip
nodeAttributes={ nodeAttributes={
Object { Object {
"class": "theme markdown__link", "className": "theme markdown__link",
"href": "http://www.dolor.com", "href": "http://www.dolor.com",
"rel": "noreferrer", "rel": "noreferrer",
"target": "_blank", "target": "_blank",

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

@@ -2,6 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {shallow} from 'enzyme'; import {shallow} from 'enzyme';
import type {AnchorHTMLAttributes} from 'react';
import AtMention from 'components/at_mention'; import AtMention from 'components/at_mention';
import MarkdownImage from 'components/markdown_image'; import MarkdownImage from 'components/markdown_image';
@@ -9,7 +10,7 @@ import MarkdownImage from 'components/markdown_image';
import {renderWithContext, screen} from 'tests/react_testing_utils'; import {renderWithContext, screen} from 'tests/react_testing_utils';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import EmojiMap from 'utils/emoji_map'; import EmojiMap from 'utils/emoji_map';
import messageHtmlToComponent from 'utils/message_html_to_component'; import messageHtmlToComponent, {convertPropsToReactStandard} from 'utils/message_html_to_component';
import * as TextFormatting from 'utils/text_formatting'; import * as TextFormatting from 'utils/text_formatting';
const emptyEmojiMap = new EmojiMap(new Map()); const emptyEmojiMap = new EmojiMap(new Map());
@@ -187,3 +188,58 @@ const myFunction = () => {
}); });
}); });
}); });
describe('convertPropsToReactStandard', () => {
test('converts class to className', () => {
const inputProps = {class: 'button-class'} as AnchorHTMLAttributes<HTMLAnchorElement>;
const expected = {className: 'button-class'};
expect(convertPropsToReactStandard(inputProps)).toEqual(expected);
});
test('converts for to htmlFor', () => {
const inputProps = {for: 'input-id'} as AnchorHTMLAttributes<HTMLAnchorElement>;
const expected = {htmlFor: 'input-id'};
expect(convertPropsToReactStandard(inputProps)).toEqual(expected);
});
test('converts tabindex to tabIndex', () => {
const inputProps = {tabindex: '0'} as AnchorHTMLAttributes<HTMLAnchorElement>;
const expected = {tabIndex: '0'};
expect(convertPropsToReactStandard(inputProps)).toEqual(expected);
});
test('converts readonly to readOnly', () => {
const inputProps = {readonly: true} as AnchorHTMLAttributes<HTMLAnchorElement>;
const expected = {readOnly: true};
expect(convertPropsToReactStandard(inputProps)).toEqual(expected);
});
test('keeps other properties unchanged', () => {
const inputProps = {id: 'unique-id', type: 'text'} as AnchorHTMLAttributes<HTMLAnchorElement>;
const expected = {id: 'unique-id', type: 'text'};
expect(convertPropsToReactStandard(inputProps)).toEqual(expected);
});
test('handles multiple conversions and keeps other properties', () => {
const inputProps = {
class: 'button-class',
for: 'input-id',
tabindex: '0',
readonly: true,
id: 'unique-id',
};
const expected = {
className: 'button-class',
htmlFor: 'input-id',
tabIndex: '0',
readOnly: true,
id: 'unique-id',
};
expect(convertPropsToReactStandard(inputProps)).toEqual(expected);
});
});

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

@@ -2,6 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Parser, ProcessNodeDefinitions} from 'html-to-react'; import {Parser, ProcessNodeDefinitions} from 'html-to-react';
import type {AllHTMLAttributes} from 'react';
import React from 'react'; import React from 'react';
import AtMention from 'components/at_mention'; import AtMention from 'components/at_mention';
@@ -64,7 +65,7 @@ type ProcessingInstruction = {
* - hasPluginTooltips - If specified, the LinkTooltip component is placed inside links. Defaults to false. * - hasPluginTooltips - If specified, the LinkTooltip component is placed inside links. Defaults to false.
* - channelId = If specified, to be passed along to ProfilePopover via AtMention * - channelId = If specified, to be passed along to ProfilePopover via AtMention
*/ */
export function messageHtmlToComponent(html: string, options: Options = {}) { export default function messageHtmlToComponent(html: string, options: Options = {}) {
if (!html) { if (!html) {
return null; return null;
} }
@@ -112,7 +113,7 @@ export function messageHtmlToComponent(html: string, options: Options = {}) {
shouldProcessNode: (node: any) => node.type === 'tag' && node.name === 'a' && node.attribs.href, shouldProcessNode: (node: any) => node.type === 'tag' && node.name === 'a' && node.attribs.href,
processNode: (node: any, children: any) => { processNode: (node: any, children: any) => {
return ( return (
<PluginLinkTooltip nodeAttributes={node.attribs}> <PluginLinkTooltip nodeAttributes={convertPropsToReactStandard(node.attribs)}>
{children} {children}
</PluginLinkTooltip> </PluginLinkTooltip>
); );
@@ -281,4 +282,32 @@ export function messageHtmlToComponent(html: string, options: Options = {}) {
return parser.parseWithInstructions(html, isValidNode, processingInstructions); return parser.parseWithInstructions(html, isValidNode, processingInstructions);
} }
export default messageHtmlToComponent; /**
* This function converts HTML attributes to React-specific props.
* For example, it changes 'class' to 'className'. Note that this function
* is not exhaustive and may not cover all HTML attributes. Add more cases as needed.
*/
export function convertPropsToReactStandard(propsToConvert: AllHTMLAttributes<HTMLElement>): Record<string, unknown> {
const newProps: Record<string, unknown> = {};
for (const [key, value] of Object.entries(propsToConvert)) {
switch (key) {
case 'class':
newProps.className = value;
break;
case 'for':
newProps.htmlFor = value;
break;
case 'tabindex':
newProps.tabIndex = value;
break;
case 'readonly':
newProps.readOnly = value;
break;
default:
newProps[key] = value;
}
}
return newProps;
}