Commit baea0902 authored by Denys Mishunov's avatar Denys Mishunov Committed by Simon Knox

Abstracted some methods into static

To make createInstance more lightweight in anticipation
for the Diff Editor's implementation, we need to refactor
the createInstance a bit to make it less overloaded.
parent a6b74a6c
......@@ -11,6 +11,8 @@ export const ERROR_INSTANCE_REQUIRED_FOR_EXTENSION = __(
'Editor Lite instance is required to set up an extension.',
);
export const EDITOR_READY_EVENT = 'editor-ready';
//
// EXTENSIONS' CONSTANTS
//
......
......@@ -5,7 +5,7 @@ import { defaultEditorOptions } from '~/ide/lib/editor_options';
import { registerLanguages } from '~/ide/utils';
import { joinPaths } from '~/lib/utils/url_utility';
import { clearDomElement } from './utils';
import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from './constants';
import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX, EDITOR_READY_EVENT } from './constants';
import { uuids } from '~/diffs/utils/uuids';
export default class EditorLite {
......@@ -73,6 +73,48 @@ export default class EditorLite {
});
}
static prepareInstance(el) {
if (!el) {
throw new Error(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
}
clearDomElement(el);
monacoEditor.onDidCreateEditor(() => {
delete el.dataset.editorLoading;
});
}
static manageDefaultExtensions(instance, el, extensions) {
EditorLite.loadExtensions(extensions, instance)
.then((modules) => {
if (modules) {
modules.forEach((module) => {
instance.use(module.default);
});
}
})
.then(() => {
el.dispatchEvent(new Event(EDITOR_READY_EVENT));
})
.catch((e) => {
throw e;
});
}
static createEditorModel({ blobPath, blobContent, blobGlobalId, instance } = {}) {
let model = null;
if (!instance) {
return null;
}
const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath);
const uri = Uri.file(uriFilePath);
const existingModel = monacoEditor.getModel(uri);
model = existingModel || monacoEditor.createModel(blobContent, undefined, uri);
instance.setModel(model);
return model;
}
/**
* Creates a monaco instance with the given options.
*
......@@ -90,25 +132,15 @@ export default class EditorLite {
extensions = [],
...instanceOptions
} = {}) {
if (!el) {
throw new Error(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
}
clearDomElement(el);
const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath);
const model = monacoEditor.createModel(blobContent, undefined, Uri.file(uriFilePath));
monacoEditor.onDidCreateEditor(() => {
delete el.dataset.editorLoading;
});
EditorLite.prepareInstance(el);
const instance = monacoEditor.create(el, {
...this.options,
...instanceOptions,
});
instance.setModel(model);
const model = EditorLite.createEditorModel({ blobGlobalId, blobPath, blobContent, instance });
instance.onDidDispose(() => {
const index = this.instances.findIndex((inst) => inst === instance);
this.instances.splice(index, 1);
......@@ -117,20 +149,7 @@ export default class EditorLite {
instance.updateModelLanguage = (path) => EditorLite.updateModelLanguage(path, instance);
instance.use = (args) => this.use(args, instance);
EditorLite.loadExtensions(extensions, instance)
.then((modules) => {
if (modules) {
modules.forEach((module) => {
instance.use(module.default);
});
}
})
.then(() => {
el.dispatchEvent(new Event('editor-ready'));
})
.catch((e) => {
throw e;
});
EditorLite.manageDefaultExtensions(instance, el, extensions);
this.instances.push(instance);
return instance;
......
<script>
import EditorLite from '~/vue_shared/components/editor_lite.vue';
import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext';
import { EDITOR_READY_EVENT } from '~/editor/constants';
export default {
components: {
......@@ -31,6 +32,7 @@ export default {
});
},
},
readyEvent: EDITOR_READY_EVENT,
};
</script>
<template>
......@@ -39,7 +41,7 @@ export default {
ref="editor"
:file-name="ciConfigPath"
v-bind="$attrs"
@editor-ready="onEditorReady"
@[$options.readyEvent]="onEditorReady"
v-on="$listeners"
/>
</div>
......
<script>
import { debounce } from 'lodash';
import Editor from '~/editor/editor_lite';
import { CONTENT_UPDATE_DEBOUNCE } from '~/editor/constants';
import { CONTENT_UPDATE_DEBOUNCE, EDITOR_READY_EVENT } from '~/editor/constants';
function initEditorLite({ el, ...args }) {
const editor = new Editor({
......@@ -88,6 +88,7 @@ export default {
return this.editor;
},
},
readyEvent: EDITOR_READY_EVENT,
};
</script>
<template>
......@@ -95,7 +96,7 @@ export default {
:id="`editor-lite-${fileGlobalId}`"
ref="editor"
data-editor-loading
@editor-ready="$emit('editor-ready')"
@[$options.readyEvent]="$emit($options.readyEvent)"
>
<pre class="editor-loading-content">{{ value }}</pre>
</div>
......
......@@ -4,7 +4,11 @@ import waitForPromises from 'helpers/wait_for_promises';
import Editor from '~/editor/editor_lite';
import { EditorLiteExtension } from '~/editor/extensions/editor_lite_extension_base';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from '~/editor/constants';
import {
EDITOR_LITE_INSTANCE_ERROR_NO_EL,
URI_PREFIX,
EDITOR_READY_EVENT,
} from '~/editor/constants';
describe('Base editor', () => {
let editorEl;
......@@ -43,16 +47,21 @@ describe('Base editor', () => {
let instanceSpy;
let setModel;
let dispose;
let modelsStorage;
beforeEach(() => {
setModel = jest.fn();
dispose = jest.fn();
modelsStorage = new Map();
modelSpy = jest.spyOn(monacoEditor, 'createModel').mockImplementation(() => fakeModel);
instanceSpy = jest.spyOn(monacoEditor, 'create').mockImplementation(() => ({
setModel,
dispose,
onDidDispose: jest.fn(),
}));
jest.spyOn(monacoEditor, 'getModel').mockImplementation((uri) => {
return modelsStorage.get(uri.path);
});
});
it('throws an error if no dom element is supplied', () => {
......@@ -72,6 +81,18 @@ describe('Base editor', () => {
expect(setModel).toHaveBeenCalledWith(fakeModel);
});
it('does not create a new model if a model for the path already exists', () => {
modelSpy = jest
.spyOn(monacoEditor, 'createModel')
.mockImplementation((content, lang, uri) => modelsStorage.set(uri.path, content));
const instanceOptions = { el: editorEl, blobPath, blobContent, blobGlobalId: '' };
const a = editor.createInstance(instanceOptions);
const b = editor.createInstance(instanceOptions);
expect(a === b).toBe(false);
expect(modelSpy).toHaveBeenCalledTimes(1);
});
it('initializes the instance on a supplied DOM node', () => {
editor.createInstance({ el: editorEl });
......@@ -446,7 +467,7 @@ describe('Base editor', () => {
expect(editorExtensionSpy).toHaveBeenCalledWith(expect.any(Array), expectation);
});
it('emits editor-ready event after all extensions were applied', async () => {
it('emits EDITOR_READY_EVENT event after all extensions were applied', async () => {
const calls = [];
const eventSpy = jest.fn().mockImplementation(() => {
calls.push('event');
......@@ -454,7 +475,7 @@ describe('Base editor', () => {
const useSpy = jest.spyOn(editor, 'use').mockImplementation(() => {
calls.push('use');
});
editorEl.addEventListener('editor-ready', eventSpy);
editorEl.addEventListener(EDITOR_READY_EVENT, eventSpy);
instance = instanceConstructor('foo, bar');
await waitForPromises();
expect(useSpy.mock.calls).toHaveLength(2);
......
......@@ -7,6 +7,7 @@ import {
mockProjectNamespace,
} from '../mock_data';
import { EDITOR_READY_EVENT } from '~/editor/constants';
import TextEditor from '~/pipeline_editor/components/text_editor.vue';
describe('~/pipeline_editor/components/text_editor.vue', () => {
......@@ -20,7 +21,7 @@ describe('~/pipeline_editor/components/text_editor.vue', () => {
template: '<div/>',
props: ['value', 'fileName'],
mounted() {
this.$emit('editor-ready');
this.$emit(EDITOR_READY_EVENT);
},
methods: {
getEditor: () => ({
......@@ -44,7 +45,7 @@ describe('~/pipeline_editor/components/text_editor.vue', () => {
value: mockCiYml,
},
listeners: {
'editor-ready': editorReadyListener,
[EDITOR_READY_EVENT]: editorReadyListener,
},
stubs: {
EditorLite: MockEditorLite,
......@@ -86,7 +87,7 @@ describe('~/pipeline_editor/components/text_editor.vue', () => {
});
it('bubbles up events', () => {
findEditor().vm.$emit('editor-ready');
findEditor().vm.$emit(EDITOR_READY_EVENT);
expect(editorReadyListener).toHaveBeenCalled();
});
......
......@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import EditorLite from '~/vue_shared/components/editor_lite.vue';
import Editor from '~/editor/editor_lite';
import { EDITOR_READY_EVENT } from '~/editor/constants';
jest.mock('~/editor/editor_lite');
......@@ -110,13 +111,13 @@ describe('Editor Lite component', () => {
expect(wrapper.emitted().input).toEqual([[value]]);
});
it('emits editor-ready event when the Editor Lite is ready', async () => {
it('emits EDITOR_READY_EVENT event when the Editor Lite is ready', async () => {
const el = wrapper.find({ ref: 'editor' }).element;
expect(wrapper.emitted()['editor-ready']).toBeUndefined();
expect(wrapper.emitted()[EDITOR_READY_EVENT]).toBeUndefined();
await el.dispatchEvent(new Event('editor-ready'));
await el.dispatchEvent(new Event(EDITOR_READY_EVENT));
expect(wrapper.emitted()['editor-ready']).toBeDefined();
expect(wrapper.emitted()[EDITOR_READY_EVENT]).toBeDefined();
});
it('component API `getEditor()` returns the editor instance', () => {
......
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