Skip to content

Commit

Permalink
[MM-62570] PluginLinkTooltip throws warning that class is invalid dom…
Browse files Browse the repository at this point in the history
… property (mattermost#29858)
  • Loading branch information
M-ZubairAhmed authored Jan 18, 2025
1 parent 7322fa1 commit f3d1bc0
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ exports[`messageHtmlToComponent link with enabled a tooltip plugin 1`] = `
<PluginLinkTooltip
nodeAttributes={
Object {
"class": "theme markdown__link",
"className": "theme markdown__link",
"href": "http://www.dolor.com",
"rel": "noreferrer",
"target": "_blank",
Expand Down
58 changes: 57 additions & 1 deletion webapp/channels/src/utils/message_html_to_component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// See LICENSE.txt for license information.

import {shallow} from 'enzyme';
import type {AnchorHTMLAttributes} from 'react';

import AtMention from 'components/at_mention';
import MarkdownImage from 'components/markdown_image';

import {renderWithContext, screen} from 'tests/react_testing_utils';
import Constants from 'utils/constants';
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';

const emptyEmojiMap = new EmojiMap(new Map());
Expand Down Expand Up @@ -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);
});
});
35 changes: 32 additions & 3 deletions webapp/channels/src/utils/message_html_to_component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See LICENSE.txt for license information.

import {Parser, ProcessNodeDefinitions} from 'html-to-react';
import type {AllHTMLAttributes} from 'react';
import React from 'react';

import AtMention from 'components/at_mention';
Expand Down Expand Up @@ -64,7 +65,7 @@ type ProcessingInstruction = {
* - hasPluginTooltips - If specified, the LinkTooltip component is placed inside links. Defaults to false.
* - 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) {
return null;
}
Expand Down Expand Up @@ -112,7 +113,7 @@ export function messageHtmlToComponent(html: string, options: Options = {}) {
shouldProcessNode: (node: any) => node.type === 'tag' && node.name === 'a' && node.attribs.href,
processNode: (node: any, children: any) => {
return (
<PluginLinkTooltip nodeAttributes={node.attribs}>
<PluginLinkTooltip nodeAttributes={convertPropsToReactStandard(node.attribs)}>
{children}
</PluginLinkTooltip>
);
Expand Down Expand Up @@ -281,4 +282,32 @@ export function messageHtmlToComponent(html: string, options: Options = {}) {
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;
}

0 comments on commit f3d1bc0

Please sign in to comment.