diff options
Diffstat (limited to 'spec/frontend/editor/source_editor_markdown_ext_spec.js')
-rw-r--r-- | spec/frontend/editor/source_editor_markdown_ext_spec.js | 368 |
1 files changed, 2 insertions, 366 deletions
diff --git a/spec/frontend/editor/source_editor_markdown_ext_spec.js b/spec/frontend/editor/source_editor_markdown_ext_spec.js index 245c6c28d31..eecd23bff6e 100644 --- a/spec/frontend/editor/source_editor_markdown_ext_spec.js +++ b/spec/frontend/editor/source_editor_markdown_ext_spec.js @@ -1,36 +1,19 @@ import MockAdapter from 'axios-mock-adapter'; -import { Range, Position, editor as monacoEditor } from 'monaco-editor'; -import waitForPromises from 'helpers/wait_for_promises'; -import { - EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS, - EXTENSION_MARKDOWN_PREVIEW_ACTION_ID, - EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH, - EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS, - EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY, -} from '~/editor/constants'; +import { Range, Position } from 'monaco-editor'; import { EditorMarkdownExtension } from '~/editor/extensions/source_editor_markdown_ext'; import SourceEditor from '~/editor/source_editor'; -import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import syntaxHighlight from '~/syntax_highlight'; - -jest.mock('~/syntax_highlight'); -jest.mock('~/flash'); describe('Markdown Extension for Source Editor', () => { let editor; let instance; let editorEl; - let panelSpy; let mockAxios; - const previewMarkdownPath = '/gitlab/fooGroup/barProj/preview_markdown'; const firstLine = 'This is a'; const secondLine = 'multiline'; const thirdLine = 'string with some **markup**'; const text = `${firstLine}\n${secondLine}\n${thirdLine}`; - const plaintextPath = 'foo.txt'; const markdownPath = 'foo.md'; - const responseData = '<div>FooBar</div>'; const setSelection = (startLineNumber = 1, startColumn = 1, endLineNumber = 1, endColumn = 1) => { const selection = new Range(startLineNumber, startColumn, endLineNumber, endColumn); @@ -42,11 +25,6 @@ describe('Markdown Extension for Source Editor', () => { const selectionToString = () => instance.getSelection().toString(); const positionToString = () => instance.getPosition().toString(); - const togglePreview = async () => { - instance.togglePreview(); - await waitForPromises(); - }; - beforeEach(() => { mockAxios = new MockAdapter(axios); setFixtures('<div id="editor" data-editor-loading></div>'); @@ -57,8 +35,7 @@ describe('Markdown Extension for Source Editor', () => { blobPath: markdownPath, blobContent: text, }); - editor.use(new EditorMarkdownExtension({ instance, previewMarkdownPath })); - panelSpy = jest.spyOn(EditorMarkdownExtension, 'togglePreviewPanel'); + instance.use({ definition: EditorMarkdownExtension }); }); afterEach(() => { @@ -67,345 +44,6 @@ describe('Markdown Extension for Source Editor', () => { mockAxios.restore(); }); - it('sets up the instance', () => { - expect(instance.preview).toEqual({ - el: undefined, - action: expect.any(Object), - shown: false, - modelChangeListener: undefined, - }); - expect(instance.previewMarkdownPath).toBe(previewMarkdownPath); - }); - - describe('model language changes listener', () => { - let cleanupSpy; - let actionSpy; - - beforeEach(async () => { - cleanupSpy = jest.spyOn(instance, 'cleanup'); - actionSpy = jest.spyOn(instance, 'setupPreviewAction'); - await togglePreview(); - }); - - it('cleans up when switching away from markdown', () => { - expect(instance.cleanup).not.toHaveBeenCalled(); - expect(instance.setupPreviewAction).not.toHaveBeenCalled(); - - instance.updateModelLanguage(plaintextPath); - - expect(cleanupSpy).toHaveBeenCalled(); - expect(actionSpy).not.toHaveBeenCalled(); - }); - - it.each` - oldLanguage | newLanguage | setupCalledTimes - ${'plaintext'} | ${'markdown'} | ${1} - ${'markdown'} | ${'markdown'} | ${0} - ${'markdown'} | ${'plaintext'} | ${0} - ${'markdown'} | ${undefined} | ${0} - ${undefined} | ${'markdown'} | ${1} - `( - 'correctly handles re-enabling of the action when switching from $oldLanguage to $newLanguage', - ({ oldLanguage, newLanguage, setupCalledTimes } = {}) => { - expect(actionSpy).not.toHaveBeenCalled(); - instance.updateModelLanguage(oldLanguage); - instance.updateModelLanguage(newLanguage); - expect(actionSpy).toHaveBeenCalledTimes(setupCalledTimes); - }, - ); - }); - - describe('model change listener', () => { - let cleanupSpy; - let actionSpy; - - beforeEach(() => { - cleanupSpy = jest.spyOn(instance, 'cleanup'); - actionSpy = jest.spyOn(instance, 'setupPreviewAction'); - instance.togglePreview(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('does not do anything if there is no model', () => { - instance.setModel(null); - - expect(cleanupSpy).not.toHaveBeenCalled(); - expect(actionSpy).not.toHaveBeenCalled(); - }); - - it('cleans up the preview when the model changes', () => { - instance.setModel(monacoEditor.createModel('foo')); - expect(cleanupSpy).toHaveBeenCalled(); - }); - - it.each` - language | setupCalledTimes - ${'markdown'} | ${1} - ${'plaintext'} | ${0} - ${undefined} | ${0} - `( - 'correctly handles actions when the new model is $language', - ({ language, setupCalledTimes } = {}) => { - instance.setModel(monacoEditor.createModel('foo', language)); - - expect(actionSpy).toHaveBeenCalledTimes(setupCalledTimes); - }, - ); - }); - - describe('cleanup', () => { - beforeEach(async () => { - mockAxios.onPost().reply(200, { body: responseData }); - await togglePreview(); - }); - - it('disposes the modelChange listener and does not fetch preview on content changes', () => { - expect(instance.preview.modelChangeListener).toBeDefined(); - jest.spyOn(instance, 'fetchPreview'); - - instance.cleanup(); - instance.setValue('Foo Bar'); - jest.advanceTimersByTime(EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY); - - expect(instance.fetchPreview).not.toHaveBeenCalled(); - }); - - it('removes the contextual menu action', () => { - expect(instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)).toBeDefined(); - - instance.cleanup(); - - expect(instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)).toBe(null); - }); - - it('toggles the `shown` flag', () => { - expect(instance.preview.shown).toBe(true); - instance.cleanup(); - expect(instance.preview.shown).toBe(false); - }); - - it('toggles the panel only if the preview is visible', () => { - const { el: previewEl } = instance.preview; - const parentEl = previewEl.parentElement; - - expect(previewEl).toBeVisible(); - expect(parentEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe(true); - - instance.cleanup(); - expect(previewEl).toBeHidden(); - expect(parentEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe( - false, - ); - - instance.cleanup(); - expect(previewEl).toBeHidden(); - expect(parentEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe( - false, - ); - }); - - it('toggles the layout only if the preview is visible', () => { - const { width } = instance.getLayoutInfo(); - - expect(instance.preview.shown).toBe(true); - - instance.cleanup(); - - const { width: newWidth } = instance.getLayoutInfo(); - expect(newWidth === width / EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH).toBe(true); - - instance.cleanup(); - expect(newWidth === width / EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH).toBe(true); - }); - }); - - describe('fetchPreview', () => { - const fetchPreview = async () => { - instance.fetchPreview(); - await waitForPromises(); - }; - - let previewMarkdownSpy; - - beforeEach(() => { - previewMarkdownSpy = jest.fn().mockImplementation(() => [200, { body: responseData }]); - mockAxios.onPost(previewMarkdownPath).replyOnce((req) => previewMarkdownSpy(req)); - }); - - it('correctly fetches preview based on previewMarkdownPath', async () => { - await fetchPreview(); - - expect(previewMarkdownSpy).toHaveBeenCalledWith( - expect.objectContaining({ data: JSON.stringify({ text }) }), - ); - }); - - it('puts the fetched content into the preview DOM element', async () => { - instance.preview.el = editorEl.parentElement; - await fetchPreview(); - expect(instance.preview.el.innerHTML).toEqual(responseData); - }); - - it('applies syntax highlighting to the preview content', async () => { - instance.preview.el = editorEl.parentElement; - await fetchPreview(); - expect(syntaxHighlight).toHaveBeenCalled(); - }); - - it('catches the errors when fetching the preview', async () => { - mockAxios.onPost().reply(500); - - await fetchPreview(); - expect(createFlash).toHaveBeenCalled(); - }); - }); - - describe('setupPreviewAction', () => { - it('adds the contextual menu action', () => { - expect(instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)).toBeDefined(); - }); - - it('does not set up action if one already exists', () => { - jest.spyOn(instance, 'addAction').mockImplementation(); - - instance.setupPreviewAction(); - expect(instance.addAction).not.toHaveBeenCalled(); - }); - - it('toggles preview when the action is triggered', () => { - jest.spyOn(instance, 'togglePreview').mockImplementation(); - - expect(instance.togglePreview).not.toHaveBeenCalled(); - - const action = instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID); - action.run(); - - expect(instance.togglePreview).toHaveBeenCalled(); - }); - }); - - describe('togglePreview', () => { - beforeEach(() => { - mockAxios.onPost().reply(200, { body: responseData }); - }); - - it('toggles preview flag on instance', () => { - expect(instance.preview.shown).toBe(false); - - instance.togglePreview(); - expect(instance.preview.shown).toBe(true); - - instance.togglePreview(); - expect(instance.preview.shown).toBe(false); - }); - - describe('panel DOM element set up', () => { - it('sets up an element to contain the preview and stores it on instance', () => { - expect(instance.preview.el).toBeUndefined(); - - instance.togglePreview(); - - expect(instance.preview.el).toBeDefined(); - expect(instance.preview.el.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS)).toBe( - true, - ); - }); - - it('re-uses existing preview DOM element on repeated calls', () => { - instance.togglePreview(); - const origPreviewEl = instance.preview.el; - instance.togglePreview(); - - expect(instance.preview.el).toBe(origPreviewEl); - }); - - it('hides the preview DOM element by default', () => { - panelSpy.mockImplementation(); - instance.togglePreview(); - expect(instance.preview.el.style.display).toBe('none'); - }); - }); - - describe('preview layout setup', () => { - it('sets correct preview layout', () => { - jest.spyOn(instance, 'layout'); - const { width, height } = instance.getLayoutInfo(); - - instance.togglePreview(); - - expect(instance.layout).toHaveBeenCalledWith({ - width: width * EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH, - height, - }); - }); - }); - - describe('preview panel', () => { - it('toggles preview CSS class on the editor', () => { - expect(editorEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe( - false, - ); - instance.togglePreview(); - expect(editorEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe( - true, - ); - instance.togglePreview(); - expect(editorEl.classList.contains(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS)).toBe( - false, - ); - }); - - it('toggles visibility of the preview DOM element', async () => { - await togglePreview(); - expect(instance.preview.el.style.display).toBe('block'); - await togglePreview(); - expect(instance.preview.el.style.display).toBe('none'); - }); - - describe('hidden preview DOM element', () => { - it('listens to model changes and re-fetches preview', async () => { - expect(mockAxios.history.post).toHaveLength(0); - await togglePreview(); - expect(mockAxios.history.post).toHaveLength(1); - - instance.setValue('New Value'); - await waitForPromises(); - expect(mockAxios.history.post).toHaveLength(2); - }); - - it('stores disposable listener for model changes', async () => { - expect(instance.preview.modelChangeListener).toBeUndefined(); - await togglePreview(); - expect(instance.preview.modelChangeListener).toBeDefined(); - }); - }); - - describe('already visible preview', () => { - beforeEach(async () => { - await togglePreview(); - mockAxios.resetHistory(); - }); - - it('does not re-fetch the preview', () => { - instance.togglePreview(); - expect(mockAxios.history.post).toHaveLength(0); - }); - - it('disposes the model change event listener', () => { - const disposeSpy = jest.fn(); - instance.preview.modelChangeListener = { - dispose: disposeSpy, - }; - instance.togglePreview(); - expect(disposeSpy).toHaveBeenCalled(); - }); - }); - }); - }); - describe('getSelectedText', () => { it('does not fail if there is no selection and returns the empty string', () => { jest.spyOn(instance, 'getSelection'); @@ -525,13 +163,11 @@ describe('Markdown Extension for Source Editor', () => { }); it('does not fail when only `toSelect` is supplied and fetches the text from selection', () => { - jest.spyOn(instance, 'getSelectedText'); const toSelect = 'string'; selectSecondAndThirdLines(); instance.selectWithinSelection(toSelect); - expect(instance.getSelectedText).toHaveBeenCalled(); expect(selectionToString()).toBe(`[3,1 -> 3,${toSelect.length + 1}]`); }); |