diff --git a/superset-frontend/packages/superset-ui-core/src/utils/html.test.tsx b/superset-frontend/packages/superset-ui-core/src/utils/html.test.tsx
index 3f38e8c9c162..16adc2dd7edd 100644
--- a/superset-frontend/packages/superset-ui-core/src/utils/html.test.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/utils/html.test.tsx
@@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
+import React from 'react';
+import { render } from '@testing-library/react';
import {
sanitizeHtml,
isProbablyHTML,
@@ -24,6 +26,7 @@ import {
removeHTMLTags,
isJsonString,
getParagraphContents,
+ escapeHtml,
} from './html';
describe('sanitizeHtml', () => {
@@ -65,16 +68,33 @@ describe('sanitizeHtmlIfNeeded', () => {
});
});
+describe('escapeHtml', () => {
+ test('should escape HTML special characters', () => {
+ const htmlString = '
test
';
+ const escaped = escapeHtml(htmlString);
+ expect(escaped).toBe('<div>test</div>');
+ });
+
+ test('should escape all special characters', () => {
+ const testString = '<>&"\'/';
+ const escaped = escapeHtml(testString);
+ expect(escaped).toBe('<>&"'/');
+ });
+
+ test('should not escape regular text', () => {
+ const plainText = 'Just a plain text';
+ const escaped = escapeHtml(plainText);
+ expect(escaped).toBe(plainText);
+ });
+});
+
describe('safeHtmlSpan', () => {
- test('should return a safe HTML span when the input is HTML', () => {
+ test('should render HTML content as a span element to display as text', () => {
const htmlString = 'Some HTML content
';
- const safeSpan = safeHtmlSpan(htmlString);
- expect(safeSpan).toEqual(
- ,
- );
+ const result = safeHtmlSpan(htmlString);
+ const { container } = render(result as React.ReactElement);
+ // The span should contain the raw HTML string as text content
+ expect(container.textContent).toBe(htmlString);
});
test('should return the input string as is when it is not HTML', () => {
@@ -82,6 +102,14 @@ describe('safeHtmlSpan', () => {
const result = safeHtmlSpan(plainText);
expect(result).toEqual(plainText);
});
+
+ test('should preserve HTML tags as visible text', () => {
+ const htmlString = 'test
';
+ const result = safeHtmlSpan(htmlString);
+ const { container } = render(result as React.ReactElement);
+ // Should display test
as text
+ expect(container.textContent).toBe(htmlString);
+ });
});
describe('removeHTMLTags', () => {
diff --git a/superset-frontend/packages/superset-ui-core/src/utils/html.tsx b/superset-frontend/packages/superset-ui-core/src/utils/html.tsx
index 723c0dc3d968..045e9b85f30d 100644
--- a/superset-frontend/packages/superset-ui-core/src/utils/html.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/utils/html.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import React from 'react';
import { FilterXSS, getDefaultWhiteList } from 'xss';
const xssFilter = new FilterXSS({
@@ -70,15 +71,27 @@ export function sanitizeHtmlIfNeeded(htmlString: string) {
return isProbablyHTML(htmlString) ? sanitizeHtml(htmlString) : htmlString;
}
-export function safeHtmlSpan(possiblyHtmlString: string) {
+/**
+ * Escapes HTML special characters to display them as text
+ * Converts < to <, > to >, & to &, etc.
+ */
+export function escapeHtml(text: string): string {
+ const map: Record = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '/': '/',
+ };
+ return text.replace(/[&<>"'/]/g, char => map[char]);
+}
+
+export function safeHtmlSpan(possiblyHtmlString: string): string | JSX.Element {
const isHtml = isProbablyHTML(possiblyHtmlString);
if (isHtml) {
- return (
-
- );
+ // Render as plain text to display the raw HTML string without interpreting it
+ return {possiblyHtmlString};
}
return possiblyHtmlString;
}