Commit 9401b60f authored by Jacques Erasmus's avatar Jacques Erasmus Committed by Marcia Ramos

Add the ability to insert a YouTube video

Added the ability to insert a youtube video
parent 1fb57db4
...@@ -15,7 +15,7 @@ const markPrefix = `${marker}-${Date.now()}`; ...@@ -15,7 +15,7 @@ const markPrefix = `${marker}-${Date.now()}`;
const reHelpers = { const reHelpers = {
template: `.| |\\t|\\n(?!(\\n|${markPrefix}))`, template: `.| |\\t|\\n(?!(\\n|${markPrefix}))`,
openTag: '<(?!iframe)[a-zA-Z]+.*?>', openTag: '<(?!figure|iframe)[a-zA-Z]+.*?>',
closeTag: '</.+>', closeTag: '</.+>',
}; };
const reTemplated = new RegExp(`(^${wrapPrefix}(${reHelpers.template})+?${wrapPostfix}$)`, 'gm'); const reTemplated = new RegExp(`(^${wrapPrefix}(${reHelpers.template})+?${wrapPostfix}$)`, 'gm');
......
...@@ -2,9 +2,14 @@ import { __ } from '~/locale'; ...@@ -2,9 +2,14 @@ import { __ } from '~/locale';
export const CUSTOM_EVENTS = { export const CUSTOM_EVENTS = {
openAddImageModal: 'gl_openAddImageModal', openAddImageModal: 'gl_openAddImageModal',
openInsertVideoModal: 'gl_openInsertVideoModal',
}; };
export const ALLOWED_VIDEO_ORIGINS = ['https://www.youtube.com']; export const YOUTUBE_URL = 'https://www.youtube.com';
export const YOUTUBE_EMBED_URL = `${YOUTUBE_URL}/embed`;
export const ALLOWED_VIDEO_ORIGINS = [YOUTUBE_URL];
/* eslint-disable @gitlab/require-i18n-strings */ /* eslint-disable @gitlab/require-i18n-strings */
export const TOOLBAR_ITEM_CONFIGS = [ export const TOOLBAR_ITEM_CONFIGS = [
...@@ -25,6 +30,7 @@ export const TOOLBAR_ITEM_CONFIGS = [ ...@@ -25,6 +30,7 @@ export const TOOLBAR_ITEM_CONFIGS = [
{ icon: 'dash', command: 'HR', tooltip: __('Add a line') }, { icon: 'dash', command: 'HR', tooltip: __('Add a line') },
{ icon: 'table', event: 'openPopupAddTable', classes: 'tui-table', tooltip: __('Add a table') }, { icon: 'table', event: 'openPopupAddTable', classes: 'tui-table', tooltip: __('Add a table') },
{ icon: 'doc-image', event: CUSTOM_EVENTS.openAddImageModal, tooltip: __('Insert an image') }, { icon: 'doc-image', event: CUSTOM_EVENTS.openAddImageModal, tooltip: __('Insert an image') },
{ icon: 'live-preview', event: CUSTOM_EVENTS.openInsertVideoModal, tooltip: __('Insert video') },
{ isDivider: true }, { isDivider: true },
{ icon: 'code', command: 'Code', tooltip: __('Insert inline code') }, { icon: 'code', command: 'Code', tooltip: __('Insert inline code') },
{ icon: 'doc-code', command: 'CodeBlock', tooltip: __('Insert a code block') }, { icon: 'doc-code', command: 'CodeBlock', tooltip: __('Insert a code block') },
...@@ -42,3 +48,10 @@ export const EDITOR_PREVIEW_STYLE = 'horizontal'; ...@@ -42,3 +48,10 @@ export const EDITOR_PREVIEW_STYLE = 'horizontal';
export const IMAGE_TABS = { UPLOAD_TAB: 0, URL_TAB: 1 }; export const IMAGE_TABS = { UPLOAD_TAB: 0, URL_TAB: 1 };
export const MAX_FILE_SIZE = 2097152; // 2Mb export const MAX_FILE_SIZE = 2097152; // 2Mb
export const VIDEO_ATTRIBUTES = {
width: '560',
height: '315',
frameBorder: '0',
allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
};
<script>
import { GlModal, GlFormGroup, GlFormInput, GlSprintf } from '@gitlab/ui';
import { isSafeURL } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { YOUTUBE_URL, YOUTUBE_EMBED_URL } from '../constants';
export default {
components: {
GlModal,
GlFormGroup,
GlFormInput,
GlSprintf,
},
data() {
return {
url: null,
urlError: null,
description: __(
'If the YouTube URL is https://www.youtube.com/watch?v=0t1DgySidms then the video ID is %{id}',
),
};
},
modalTitle: __('Insert a video'),
okTitle: __('Insert video'),
label: __('YouTube URL or ID'),
methods: {
show() {
this.urlError = null;
this.url = null;
this.$refs.modal.show();
},
onPrimary(event) {
this.submitURL(event);
},
submitURL(event) {
const url = this.generateUrl();
if (!url) {
event.preventDefault();
return;
}
this.$emit('insertVideo', url);
},
generateUrl() {
let { url } = this;
const reYouTubeId = /^[A-z0-9]*$/;
const reYouTubeUrl = RegExp(`${YOUTUBE_URL}/(embed/|watch\\?v=)([A-z0-9]+)`);
if (reYouTubeId.test(url)) {
url = `${YOUTUBE_EMBED_URL}/${url}`;
} else if (reYouTubeUrl.test(url)) {
url = `${YOUTUBE_EMBED_URL}/${reYouTubeUrl.exec(url)[2]}`;
}
if (!isSafeURL(url) || !reYouTubeUrl.test(url)) {
this.urlError = __('Please provide a valid YouTube URL or ID');
this.$refs.urlInput.$el.focus();
return null;
}
return url;
},
},
};
</script>
<template>
<gl-modal
ref="modal"
size="sm"
modal-id="insert-video-modal"
:title="$options.modalTitle"
:ok-title="$options.okTitle"
@primary="onPrimary"
>
<gl-form-group
:label="$options.label"
label-for="video-modal-url-input"
:state="!Boolean(urlError)"
:invalid-feedback="urlError"
>
<gl-form-input id="video-modal-url-input" ref="urlInput" v-model="url" />
<gl-sprintf slot="description" :message="description" class="text-gl-muted">
<template #id>
<strong>{{ __('0t1DgySidms') }}</strong>
</template>
</gl-sprintf>
</gl-form-group>
</gl-modal>
</template>
...@@ -3,6 +3,7 @@ import 'codemirror/lib/codemirror.css'; ...@@ -3,6 +3,7 @@ import 'codemirror/lib/codemirror.css';
import '@toast-ui/editor/dist/toastui-editor.css'; import '@toast-ui/editor/dist/toastui-editor.css';
import AddImageModal from './modals/add_image/add_image_modal.vue'; import AddImageModal from './modals/add_image/add_image_modal.vue';
import InsertVideoModal from './modals/insert_video_modal.vue';
import { EDITOR_TYPES, EDITOR_HEIGHT, EDITOR_PREVIEW_STYLE, CUSTOM_EVENTS } from './constants'; import { EDITOR_TYPES, EDITOR_HEIGHT, EDITOR_PREVIEW_STYLE, CUSTOM_EVENTS } from './constants';
import { import {
...@@ -12,6 +13,7 @@ import { ...@@ -12,6 +13,7 @@ import {
removeCustomEventListener, removeCustomEventListener,
addImage, addImage,
getMarkdown, getMarkdown,
insertVideo,
} from './services/editor_service'; } from './services/editor_service';
export default { export default {
...@@ -21,6 +23,7 @@ export default { ...@@ -21,6 +23,7 @@ export default {
toast => toast.Editor, toast => toast.Editor,
), ),
AddImageModal, AddImageModal,
InsertVideoModal,
}, },
props: { props: {
content: { content: {
...@@ -63,6 +66,12 @@ export default { ...@@ -63,6 +66,12 @@ export default {
editorInstance() { editorInstance() {
return this.$refs.editor; return this.$refs.editor;
}, },
customEventListeners() {
return [
{ event: CUSTOM_EVENTS.openAddImageModal, listener: this.onOpenAddImageModal },
{ event: CUSTOM_EVENTS.openInsertVideoModal, listener: this.onOpenInsertVideoModal },
];
},
}, },
created() { created() {
this.editorOptions = getEditorOptions(this.options); this.editorOptions = getEditorOptions(this.options);
...@@ -72,16 +81,16 @@ export default { ...@@ -72,16 +81,16 @@ export default {
}, },
methods: { methods: {
addListeners(editorApi) { addListeners(editorApi) {
addCustomEventListener(editorApi, CUSTOM_EVENTS.openAddImageModal, this.onOpenAddImageModal); this.customEventListeners.forEach(({ event, listener }) => {
addCustomEventListener(editorApi, event, listener);
});
editorApi.eventManager.listen('changeMode', this.onChangeMode); editorApi.eventManager.listen('changeMode', this.onChangeMode);
}, },
removeListeners() { removeListeners() {
removeCustomEventListener( this.customEventListeners.forEach(({ event, listener }) => {
this.editorApi, removeCustomEventListener(this.editorApi, event, listener);
CUSTOM_EVENTS.openAddImageModal, });
this.onOpenAddImageModal,
);
this.editorApi.eventManager.removeEventHandler('changeMode', this.onChangeMode); this.editorApi.eventManager.removeEventHandler('changeMode', this.onChangeMode);
}, },
...@@ -111,6 +120,12 @@ export default { ...@@ -111,6 +120,12 @@ export default {
addImage(this.editorInstance, image); addImage(this.editorInstance, image);
}, },
onOpenInsertVideoModal() {
this.$refs.insertVideoModal.show();
},
onInsertVideo(url) {
insertVideo(this.editorInstance, url);
},
onChangeMode(newMode) { onChangeMode(newMode) {
this.$emit('modeChange', newMode); this.$emit('modeChange', newMode);
}, },
...@@ -130,5 +145,6 @@ export default { ...@@ -130,5 +145,6 @@ export default {
@load="onLoad" @load="onLoad"
/> />
<add-image-modal ref="addImageModal" :image-root="imageRoot" @addImage="onAddImage" /> <add-image-modal ref="addImageModal" :image-root="imageRoot" @addImage="onAddImage" />
<insert-video-modal ref="insertVideoModal" @insertVideo="onInsertVideo" />
</div> </div>
</template> </template>
...@@ -3,7 +3,7 @@ import { defaults } from 'lodash'; ...@@ -3,7 +3,7 @@ import { defaults } from 'lodash';
import ToolbarItem from '../toolbar_item.vue'; import ToolbarItem from '../toolbar_item.vue';
import buildHtmlToMarkdownRenderer from './build_html_to_markdown_renderer'; import buildHtmlToMarkdownRenderer from './build_html_to_markdown_renderer';
import buildCustomHTMLRenderer from './build_custom_renderer'; import buildCustomHTMLRenderer from './build_custom_renderer';
import { TOOLBAR_ITEM_CONFIGS } from '../constants'; import { TOOLBAR_ITEM_CONFIGS, VIDEO_ATTRIBUTES } from '../constants';
import sanitizeHTML from './sanitize_html'; import sanitizeHTML from './sanitize_html';
const buildWrapper = propsData => { const buildWrapper = propsData => {
...@@ -17,6 +17,23 @@ const buildWrapper = propsData => { ...@@ -17,6 +17,23 @@ const buildWrapper = propsData => {
return instance.$el; return instance.$el;
}; };
const buildVideoIframe = src => {
const wrapper = document.createElement('figure');
const iframe = document.createElement('iframe');
const videoAttributes = { ...VIDEO_ATTRIBUTES, src };
const wrapperClasses = ['gl-relative', 'gl-h-0', 'video_container'];
const iframeClasses = ['gl-absolute', 'gl-top-0', 'gl-left-0', 'gl-w-full', 'gl-h-full'];
wrapper.setAttribute('contenteditable', 'false');
wrapper.classList.add(...wrapperClasses);
iframe.classList.add(...iframeClasses);
Object.assign(iframe, videoAttributes);
wrapper.appendChild(iframe);
return wrapper;
};
export const generateToolbarItem = config => { export const generateToolbarItem = config => {
const { icon, classes, event, command, tooltip, isDivider } = config; const { icon, classes, event, command, tooltip, isDivider } = config;
...@@ -44,6 +61,16 @@ export const removeCustomEventListener = (editorApi, event, handler) => ...@@ -44,6 +61,16 @@ export const removeCustomEventListener = (editorApi, event, handler) =>
export const addImage = ({ editor }, image) => editor.exec('AddImage', image); export const addImage = ({ editor }, image) => editor.exec('AddImage', image);
export const insertVideo = ({ editor }, url) => {
const videoIframe = buildVideoIframe(url);
if (editor.isWysiwygMode()) {
editor.getSquire().insertElement(videoIframe);
} else {
editor.insertText(videoIframe.outerHTML);
}
};
export const getMarkdown = editorInstance => editorInstance.invoke('getMarkdown'); export const getMarkdown = editorInstance => editorInstance.invoke('getMarkdown');
/** /**
......
...@@ -44,3 +44,11 @@ ...@@ -44,3 +44,11 @@
@include gl-line-height-20; @include gl-line-height-20;
} }
} }
/**
* Styling below ensures that YouTube videos are displayed in the editor the same as they would in about.gitlab.com
* https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/master/source/stylesheets/_base.scss#L977
*/
.video_container {
padding-bottom: 56.25%;
}
---
title: Add the ability to insert a YouTube video
merge_request: 44102
author:
type: added
...@@ -115,6 +115,17 @@ company and a new feature has been added to the company product. ...@@ -115,6 +115,17 @@ company and a new feature has been added to the company product.
1. You edit the file right there and click **Submit changes**. 1. You edit the file right there and click **Submit changes**.
1. A new merge request is automatically created and you assign it to your colleague for review. 1. A new merge request is automatically created and you assign it to your colleague for review.
## Videos
> - Support for embedding YouTube videos through the WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216642) in GitLab 13.5.
You can embed YouTube videos on the WYSIWYG mode by clicking the video icon (**{live-preview}**).
The following URL/ID formats are supported:
- YouTube watch URL (e.g. `https://www.youtube.com/watch?v=0t1DgySidms`)
- YouTube embed URL (e.g. `https://www.youtube.com/embed/0t1DgySidms`)
- YouTube video ID (e.g. `0t1DgySidms`)
## Limitations ## Limitations
- The Static Site Editor still cannot be quickly added to existing Middleman sites. Follow this [epic](https://gitlab.com/groups/gitlab-org/-/epics/2784) for updates. - The Static Site Editor still cannot be quickly added to existing Middleman sites. Follow this [epic](https://gitlab.com/groups/gitlab-org/-/epics/2784) for updates.
...@@ -1051,6 +1051,9 @@ msgstr "" ...@@ -1051,6 +1051,9 @@ msgstr ""
msgid "0 for unlimited, only effective with remote storage enabled." msgid "0 for unlimited, only effective with remote storage enabled."
msgstr "" msgstr ""
msgid "0t1DgySidms"
msgstr ""
msgid "1 %{type} addition" msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions" msgid_plural "%{count} %{type} additions"
msgstr[0] "" msgstr[0] ""
...@@ -13549,6 +13552,9 @@ msgstr "" ...@@ -13549,6 +13552,9 @@ msgstr ""
msgid "If enabled, access to projects will be validated on an external service using their classification label." msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr "" msgstr ""
msgid "If the YouTube URL is https://www.youtube.com/watch?v=0t1DgySidms then the video ID is %{id}"
msgstr ""
msgid "If the number of active users exceeds the user limit, you will be charged for the number of %{users_over_license_link} at your next license reconciliation." msgid "If the number of active users exceeds the user limit, you will be charged for the number of %{users_over_license_link} at your next license reconciliation."
msgstr "" msgstr ""
...@@ -14021,6 +14027,9 @@ msgstr "" ...@@ -14021,6 +14027,9 @@ msgstr ""
msgid "Insert a quote" msgid "Insert a quote"
msgstr "" msgstr ""
msgid "Insert a video"
msgstr ""
msgid "Insert an image" msgid "Insert an image"
msgstr "" msgstr ""
...@@ -14036,6 +14045,9 @@ msgstr "" ...@@ -14036,6 +14045,9 @@ msgstr ""
msgid "Insert suggestion" msgid "Insert suggestion"
msgstr "" msgstr ""
msgid "Insert video"
msgstr ""
msgid "Insights" msgid "Insights"
msgstr "" msgstr ""
...@@ -19540,6 +19552,9 @@ msgstr "" ...@@ -19540,6 +19552,9 @@ msgstr ""
msgid "Please provide a valid URL" msgid "Please provide a valid URL"
msgstr "" msgstr ""
msgid "Please provide a valid YouTube URL or ID"
msgstr ""
msgid "Please provide a valid email address." msgid "Please provide a valid email address."
msgstr "" msgstr ""
...@@ -30282,6 +30297,9 @@ msgstr "" ...@@ -30282,6 +30297,9 @@ msgstr ""
msgid "YouTube" msgid "YouTube"
msgstr "" msgstr ""
msgid "YouTube URL or ID"
msgstr ""
msgid "Your %{host} account was signed in to from a new location" msgid "Your %{host} account was signed in to from a new location"
msgstr "" msgstr ""
......
...@@ -4,6 +4,7 @@ import { ...@@ -4,6 +4,7 @@ import {
removeCustomEventListener, removeCustomEventListener,
registerHTMLToMarkdownRenderer, registerHTMLToMarkdownRenderer,
addImage, addImage,
insertVideo,
getMarkdown, getMarkdown,
getEditorOptions, getEditorOptions,
} from '~/vue_shared/components/rich_content_editor/services/editor_service'; } from '~/vue_shared/components/rich_content_editor/services/editor_service';
...@@ -19,11 +20,21 @@ describe('Editor Service', () => { ...@@ -19,11 +20,21 @@ describe('Editor Service', () => {
let mockInstance; let mockInstance;
let event; let event;
let handler; let handler;
const parseHtml = str => {
const wrapper = document.createElement('div');
wrapper.innerHTML = str;
return wrapper.firstChild;
};
beforeEach(() => { beforeEach(() => {
mockInstance = { mockInstance = {
eventManager: { addEventType: jest.fn(), removeEventHandler: jest.fn(), listen: jest.fn() }, eventManager: { addEventType: jest.fn(), removeEventHandler: jest.fn(), listen: jest.fn() },
editor: { exec: jest.fn() }, editor: {
exec: jest.fn(),
isWysiwygMode: jest.fn(),
getSquire: jest.fn(),
insertText: jest.fn(),
},
invoke: jest.fn(), invoke: jest.fn(),
toMarkOptions: { toMarkOptions: {
renderer: { renderer: {
...@@ -89,6 +100,38 @@ describe('Editor Service', () => { ...@@ -89,6 +100,38 @@ describe('Editor Service', () => {
}); });
}); });
describe('insertVideo', () => {
const mockUrl = 'some/url';
const htmlString = `<figure contenteditable="false" class="gl-relative gl-h-0 video_container"><iframe class="gl-absolute gl-top-0 gl-left-0 gl-w-full gl-h-full" width="560" height="315" frameborder="0" src="some/url"></iframe></figure>`;
const mockInsertElement = jest.fn();
beforeEach(() =>
mockInstance.editor.getSquire.mockReturnValue({ insertElement: mockInsertElement }),
);
describe('WYSIWYG mode', () => {
it('calls the insertElement method on the squire instance with an iFrame element', () => {
mockInstance.editor.isWysiwygMode.mockReturnValue(true);
insertVideo(mockInstance, mockUrl);
expect(mockInstance.editor.getSquire().insertElement).toHaveBeenCalledWith(
parseHtml(htmlString),
);
});
});
describe('Markdown mode', () => {
it('calls the insertText method on the editor instance with the iFrame element HTML', () => {
mockInstance.editor.isWysiwygMode.mockReturnValue(false);
insertVideo(mockInstance, mockUrl);
expect(mockInstance.editor.insertText).toHaveBeenCalledWith(htmlString);
});
});
});
describe('getMarkdown', () => { describe('getMarkdown', () => {
it('calls the invoke method on the instance', () => { it('calls the invoke method on the instance', () => {
getMarkdown(mockInstance); getMarkdown(mockInstance);
......
import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import InsertVideoModal from '~/vue_shared/components/rich_content_editor/modals/insert_video_modal.vue';
describe('Insert Video Modal', () => {
let wrapper;
const findModal = () => wrapper.find(GlModal);
const findUrlInput = () => wrapper.find({ ref: 'urlInput' });
const triggerInsertVideo = url => {
const preventDefault = jest.fn();
findUrlInput().vm.$emit('input', url);
findModal().vm.$emit('primary', { preventDefault });
};
beforeEach(() => {
wrapper = shallowMount(InsertVideoModal);
});
afterEach(() => wrapper.destroy());
describe('when content is loaded', () => {
it('renders a modal component', () => {
expect(findModal().exists()).toBe(true);
});
it('renders an input to add a URL', () => {
expect(findUrlInput().exists()).toBe(true);
});
});
describe('insert video', () => {
it.each`
url | emitted
${'https://www.youtube.com/embed/someId'} | ${[['https://www.youtube.com/embed/someId']]}
${'https://www.youtube.com/watch?v=1234'} | ${[['https://www.youtube.com/embed/1234']]}
${'::youtube.com/invalid/url'} | ${undefined}
`('formats the url correctly', ({ url, emitted }) => {
triggerInsertVideo(url);
expect(wrapper.emitted('insertVideo')).toEqual(emitted);
});
});
});
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue'; import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue'; import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
import InsertVideoModal from '~/vue_shared/components/rich_content_editor/modals/insert_video_modal.vue';
import { import {
EDITOR_TYPES, EDITOR_TYPES,
EDITOR_HEIGHT, EDITOR_HEIGHT,
...@@ -12,6 +13,7 @@ import { ...@@ -12,6 +13,7 @@ import {
addCustomEventListener, addCustomEventListener,
removeCustomEventListener, removeCustomEventListener,
addImage, addImage,
insertVideo,
registerHTMLToMarkdownRenderer, registerHTMLToMarkdownRenderer,
getEditorOptions, getEditorOptions,
} from '~/vue_shared/components/rich_content_editor/services/editor_service'; } from '~/vue_shared/components/rich_content_editor/services/editor_service';
...@@ -21,6 +23,7 @@ jest.mock('~/vue_shared/components/rich_content_editor/services/editor_service', ...@@ -21,6 +23,7 @@ jest.mock('~/vue_shared/components/rich_content_editor/services/editor_service',
addCustomEventListener: jest.fn(), addCustomEventListener: jest.fn(),
removeCustomEventListener: jest.fn(), removeCustomEventListener: jest.fn(),
addImage: jest.fn(), addImage: jest.fn(),
insertVideo: jest.fn(),
registerHTMLToMarkdownRenderer: jest.fn(), registerHTMLToMarkdownRenderer: jest.fn(),
getEditorOptions: jest.fn(), getEditorOptions: jest.fn(),
})); }));
...@@ -32,6 +35,7 @@ describe('Rich Content Editor', () => { ...@@ -32,6 +35,7 @@ describe('Rich Content Editor', () => {
const imageRoot = 'path/to/root/'; const imageRoot = 'path/to/root/';
const findEditor = () => wrapper.find({ ref: 'editor' }); const findEditor = () => wrapper.find({ ref: 'editor' });
const findAddImageModal = () => wrapper.find(AddImageModal); const findAddImageModal = () => wrapper.find(AddImageModal);
const findInsertVideoModal = () => wrapper.find(InsertVideoModal);
const buildWrapper = () => { const buildWrapper = () => {
wrapper = shallowMount(RichContentEditor, { wrapper = shallowMount(RichContentEditor, {
...@@ -122,6 +126,14 @@ describe('Rich Content Editor', () => { ...@@ -122,6 +126,14 @@ describe('Rich Content Editor', () => {
); );
}); });
it('adds the CUSTOM_EVENTS.openInsertVideoModal custom event listener', () => {
expect(addCustomEventListener).toHaveBeenCalledWith(
wrapper.vm.editorApi,
CUSTOM_EVENTS.openInsertVideoModal,
wrapper.vm.onOpenInsertVideoModal,
);
});
it('registers HTML to markdown renderer', () => { it('registers HTML to markdown renderer', () => {
expect(registerHTMLToMarkdownRenderer).toHaveBeenCalledWith(wrapper.vm.editorApi); expect(registerHTMLToMarkdownRenderer).toHaveBeenCalledWith(wrapper.vm.editorApi);
}); });
...@@ -141,6 +153,16 @@ describe('Rich Content Editor', () => { ...@@ -141,6 +153,16 @@ describe('Rich Content Editor', () => {
wrapper.vm.onOpenAddImageModal, wrapper.vm.onOpenAddImageModal,
); );
}); });
it('removes the CUSTOM_EVENTS.openInsertVideoModal custom event listener', () => {
wrapper.vm.$destroy();
expect(removeCustomEventListener).toHaveBeenCalledWith(
wrapper.vm.editorApi,
CUSTOM_EVENTS.openInsertVideoModal,
wrapper.vm.onOpenInsertVideoModal,
);
});
}); });
describe('add image modal', () => { describe('add image modal', () => {
...@@ -161,4 +183,23 @@ describe('Rich Content Editor', () => { ...@@ -161,4 +183,23 @@ describe('Rich Content Editor', () => {
expect(addImage).toHaveBeenCalledWith(mockInstance, mockImage); expect(addImage).toHaveBeenCalledWith(mockInstance, mockImage);
}); });
}); });
describe('insert video modal', () => {
beforeEach(() => {
buildWrapper();
});
it('renders an insertVideoModal component', () => {
expect(findInsertVideoModal().exists()).toBe(true);
});
it('calls the onInsertVideo method when the insertVideo event is emitted', () => {
const mockUrl = 'https://www.youtube.com/embed/someId';
const mockInstance = { exec: jest.fn() };
wrapper.vm.$refs.editor = mockInstance;
findInsertVideoModal().vm.$emit('insertVideo', mockUrl);
expect(insertVideo).toHaveBeenCalledWith(mockInstance, mockUrl);
});
});
}); });
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