Commit ace17073 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'fix-sse-escaping-sequences' into 'master'

Fix incorrect text escaping in Toast UI/Static Site Editor

See merge request gitlab-org/gitlab!35671
parents 97fa0e86 a59face3
......@@ -12,6 +12,7 @@ import {
} from './constants';
import {
registerHTMLToMarkdownRenderer,
addCustomEventListener,
removeCustomEventListener,
addImage,
......@@ -87,6 +88,7 @@ export default {
onLoad(editorApi) {
this.editorApi = editorApi;
registerHTMLToMarkdownRenderer(editorApi);
addCustomEventListener(
this.editorApi,
CUSTOM_EVENTS.openAddImageModal,
......
const buildHTMLToMarkdownRender = baseRenderer => {
return {
TEXT_NODE(node) {
return baseRenderer.getSpaceControlled(
baseRenderer.trim(baseRenderer.getSpaceCollapsedText(node.nodeValue)),
node,
);
},
};
};
export default buildHTMLToMarkdownRender;
import Vue from 'vue';
import ToolbarItem from '../toolbar_item.vue';
import buildHtmlToMarkdownRenderer from './build_html_to_markdown_renderer';
const buildWrapper = propsData => {
const instance = new Vue({
......@@ -40,3 +41,16 @@ export const removeCustomEventListener = (editorApi, event, handler) =>
export const addImage = ({ editor }, image) => editor.exec('AddImage', image);
export const getMarkdown = editorInstance => editorInstance.invoke('getMarkdown');
/**
* This function allow us to extend Toast UI HTML to Markdown renderer. It is
* a temporary measure because Toast UI does not provide an API
* to achieve this goal.
*/
export const registerHTMLToMarkdownRenderer = editorApi => {
const { renderer } = editorApi.toMarkOptions;
Object.assign(editorApi.toMarkOptions, {
renderer: renderer.constructor.factory(renderer, buildHtmlToMarkdownRenderer(renderer)),
});
};
---
title: Fix incorrect text escaping in the Static Site Editor
merge_request: 35671
author:
type: fixed
......@@ -2,18 +2,35 @@ import {
generateToolbarItem,
addCustomEventListener,
removeCustomEventListener,
registerHTMLToMarkdownRenderer,
addImage,
getMarkdown,
} from '~/vue_shared/components/rich_content_editor/services/editor_service';
import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer';
jest.mock('~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer');
describe('Editor Service', () => {
const mockInstance = {
eventManager: { addEventType: jest.fn(), removeEventHandler: jest.fn(), listen: jest.fn() },
editor: { exec: jest.fn() },
invoke: jest.fn(),
};
const event = 'someCustomEvent';
const handler = jest.fn();
let mockInstance;
let event;
let handler;
beforeEach(() => {
mockInstance = {
eventManager: { addEventType: jest.fn(), removeEventHandler: jest.fn(), listen: jest.fn() },
editor: { exec: jest.fn() },
invoke: jest.fn(),
toMarkOptions: {
renderer: {
constructor: {
factory: jest.fn(),
},
},
},
};
event = 'someCustomEvent';
handler = jest.fn();
});
describe('generateToolbarItem', () => {
const config = {
......@@ -74,4 +91,33 @@ describe('Editor Service', () => {
expect(mockInstance.invoke).toHaveBeenCalledWith('getMarkdown');
});
});
describe('registerHTMLToMarkdownRenderer', () => {
let baseRenderer;
const htmlToMarkdownRenderer = {};
const extendedRenderer = {};
beforeEach(() => {
baseRenderer = mockInstance.toMarkOptions.renderer;
buildHTMLToMarkdownRenderer.mockReturnValueOnce(htmlToMarkdownRenderer);
baseRenderer.constructor.factory.mockReturnValueOnce(extendedRenderer);
registerHTMLToMarkdownRenderer(mockInstance);
});
it('builds a new instance of the HTML to Markdown renderer', () => {
expect(buildHTMLToMarkdownRenderer).toHaveBeenCalledWith(baseRenderer);
});
it('extends base renderer with the HTML to Markdown renderer', () => {
expect(baseRenderer.constructor.factory).toHaveBeenCalledWith(
baseRenderer,
htmlToMarkdownRenderer,
);
});
it('replaces the default renderer with extended renderer', () => {
expect(mockInstance.toMarkOptions.renderer).toBe(extendedRenderer);
});
});
});
......@@ -13,6 +13,7 @@ import {
addCustomEventListener,
removeCustomEventListener,
addImage,
registerHTMLToMarkdownRenderer,
} from '~/vue_shared/components/rich_content_editor/services/editor_service';
jest.mock('~/vue_shared/components/rich_content_editor/services/editor_service', () => ({
......@@ -20,6 +21,7 @@ jest.mock('~/vue_shared/components/rich_content_editor/services/editor_service',
addCustomEventListener: jest.fn(),
removeCustomEventListener: jest.fn(),
addImage: jest.fn(),
registerHTMLToMarkdownRenderer: jest.fn(),
}));
describe('Rich Content Editor', () => {
......@@ -86,16 +88,24 @@ describe('Rich Content Editor', () => {
});
describe('when editor is loaded', () => {
it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
const mockEditorApi = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } };
let mockEditorApi;
beforeEach(() => {
mockEditorApi = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } };
findEditor().vm.$emit('load', mockEditorApi);
});
it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
expect(addCustomEventListener).toHaveBeenCalledWith(
mockEditorApi,
CUSTOM_EVENTS.openAddImageModal,
wrapper.vm.onOpenAddImageModal,
);
});
it('registers HTML to markdown renderer', () => {
expect(registerHTMLToMarkdownRenderer).toHaveBeenCalledWith(mockEditorApi);
});
});
describe('when editor is destroyed', () => {
......
import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer';
describe('HTMLToMarkdownRenderer', () => {
let baseRenderer;
let htmlToMarkdownRenderer;
const NODE = { nodeValue: 'mock_node' };
beforeEach(() => {
baseRenderer = {
trim: jest.fn(input => `trimmed ${input}`),
getSpaceCollapsedText: jest.fn(input => `space collapsed ${input}`),
getSpaceControlled: jest.fn(input => `space controlled ${input}`),
};
htmlToMarkdownRenderer = buildHTMLToMarkdownRenderer(baseRenderer);
});
describe('TEXT_NODE visitor', () => {
it('composes getSpaceControlled, getSpaceCollapsedText, and trim services', () => {
expect(htmlToMarkdownRenderer.TEXT_NODE(NODE)).toBe(
`space controlled trimmed space collapsed ${NODE.nodeValue}`,
);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment