Commit 9f4ef4b5 authored by Denys Mishunov's avatar Denys Mishunov

Allow using extensions from instance level

parent b89fa5ca
...@@ -39,6 +39,26 @@ export default class Editor { ...@@ -39,6 +39,26 @@ export default class Editor {
monacoEditor.setModelLanguage(model, id); monacoEditor.setModelLanguage(model, id);
} }
static pushToImportsArray(arr, toImport) {
arr.push(import(toImport));
}
static loadExtensions(extensions) {
if (!extensions) {
return Promise.resolve();
}
const promises = [];
const extensionsArray = typeof extensions === 'string' ? extensions.split(',') : extensions;
extensionsArray.forEach(ext => {
const prefix = ext.indexOf('/') === -1 ? 'editor/' : '';
const trimmedExt = ext.replace(/^\//, '').trim();
Editor.pushToImportsArray(promises, `~/${prefix}${trimmedExt}`);
});
return Promise.all(promises);
}
/** /**
* Creates a monaco instance with the given options. * Creates a monaco instance with the given options.
* *
...@@ -53,6 +73,7 @@ export default class Editor { ...@@ -53,6 +73,7 @@ export default class Editor {
blobPath = '', blobPath = '',
blobContent = '', blobContent = '',
blobGlobalId = '', blobGlobalId = '',
extensions = [],
...instanceOptions ...instanceOptions
} = {}) { } = {}) {
if (!el) { if (!el) {
...@@ -80,6 +101,22 @@ export default class Editor { ...@@ -80,6 +101,22 @@ export default class Editor {
model.dispose(); model.dispose();
}); });
instance.updateModelLanguage = path => Editor.updateModelLanguage(path, instance); instance.updateModelLanguage = path => Editor.updateModelLanguage(path, instance);
instance.use = args => this.use(args, instance);
Editor.loadExtensions(extensions, instance)
.then(modules => {
if (modules) {
modules.forEach(module => {
instance.use(module.default);
});
}
})
.catch(e => {
throw e;
})
.finally(() => {
el.dispatchEvent(new Event('editor-ready'));
});
this.instances.push(instance); this.instances.push(instance);
return instance; return instance;
......
---
title: Editor Lite to saupport extensions in instance constructor
merge_request: 44723
author:
type: added
import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor'; import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
import waitForPromises from 'helpers/wait_for_promises';
import Editor from '~/editor/editor_lite'; import Editor from '~/editor/editor_lite';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes'; 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 } from '~/editor/constants';
...@@ -253,55 +254,125 @@ describe('Base editor', () => { ...@@ -253,55 +254,125 @@ describe('Base editor', () => {
const MyExt3 = { const MyExt3 = {
foo: foo2, foo: foo2,
}; };
beforeEach(() => {
instance = editor.createInstance({ el: editorEl, blobPath, blobContent });
});
it('is extensible with the extensions', () => { describe('basic functionality', () => {
expect(instance.foo).toBeUndefined(); beforeEach(() => {
instance = editor.createInstance({ el: editorEl, blobPath, blobContent });
});
editor.use(MyExt1); it('is extensible with the extensions', () => {
expect(instance.foo).toEqual(foo1); expect(instance.foo).toBeUndefined();
});
it('does not fail if no extensions supplied', () => { instance.use(MyExt1);
const spy = jest.spyOn(global.console, 'error'); expect(instance.foo).toEqual(foo1);
editor.use(); });
expect(spy).not.toHaveBeenCalled(); it('does not fail if no extensions supplied', () => {
}); const spy = jest.spyOn(global.console, 'error');
instance.use();
it('is extensible with multiple extensions', () => { expect(spy).not.toHaveBeenCalled();
expect(instance.foo).toBeUndefined(); });
expect(instance.bar).toBeUndefined();
editor.use([MyExt1, MyExt2]); it('is extensible with multiple extensions', () => {
expect(instance.foo).toBeUndefined();
expect(instance.bar).toBeUndefined();
expect(instance.foo).toEqual(foo1); instance.use([MyExt1, MyExt2]);
expect(instance.bar).toEqual(bar);
});
it('uses the last definition of a method in case of an overlap', () => { expect(instance.foo).toEqual(foo1);
editor.use([MyExt1, MyExt2, MyExt3]); expect(instance.bar).toEqual(bar);
expect(instance).toEqual( });
expect.objectContaining({
foo: foo2, it('uses the last definition of a method in case of an overlap', () => {
bar, instance.use([MyExt1, MyExt2, MyExt3]);
}), expect(instance).toEqual(
); expect.objectContaining({
foo: foo2,
bar,
}),
);
});
it('correctly resolves references withing extensions', () => {
const FunctionExt = {
inst() {
return this;
},
mod() {
return this.getModel();
},
};
instance.use(FunctionExt);
expect(instance.inst()).toEqual(editor.instances[0]);
});
}); });
it('correctly resolves references withing extensions', () => { describe('extensions as an instance parameter', () => {
const FunctionExt = { let editorExtensionSpy;
inst() { const instanceConstructor = (extensions = []) => {
return this; return editor.createInstance({
}, el: editorEl,
mod() { blobPath,
return this.getModel(); blobContent,
}, blobGlobalId,
extensions,
});
}; };
editor.use(FunctionExt);
expect(instance.inst()).toEqual(editor.instances[0]); beforeEach(() => {
editorExtensionSpy = jest.spyOn(Editor, 'pushToImportsArray').mockImplementation(arr => {
arr.push(
Promise.resolve({
default: {},
}),
);
});
});
it.each([undefined, [], [''], ''])(
'does not fail and makes no fetch if extensions is %s',
() => {
instance = instanceConstructor(null);
expect(editorExtensionSpy).not.toHaveBeenCalled();
},
);
it.each`
type | value | callsCount
${'simple string'} | ${'foo'} | ${1}
${'combined string'} | ${'foo, bar'} | ${2}
${'array of strings'} | ${['foo', 'bar']} | ${2}
`('accepts $type as an extension parameter', ({ value, callsCount }) => {
instance = instanceConstructor(value);
expect(editorExtensionSpy).toHaveBeenCalled();
expect(editorExtensionSpy.mock.calls).toHaveLength(callsCount);
});
it.each`
desc | path | expectation
${'~/editor'} | ${'foo'} | ${'~/editor/foo'}
${'~/CUSTOM_PATH with leading slash'} | ${'/my_custom_path/bar'} | ${'~/my_custom_path/bar'}
${'~/CUSTOM_PATH without leading slash'} | ${'my_custom_path/delta'} | ${'~/my_custom_path/delta'}
`('fetches extensions from $desc path', ({ path, expectation }) => {
instance = instanceConstructor(path);
expect(editorExtensionSpy).toHaveBeenCalledWith(expect.any(Array), expectation);
});
it('emits editor-ready event after all extensions were applied', async () => {
const calls = [];
const eventSpy = jest.fn().mockImplementation(() => {
calls.push('event');
});
const useSpy = jest.spyOn(editor, 'use').mockImplementation(() => {
calls.push('use');
});
editorEl.addEventListener('editor-ready', eventSpy);
instance = instanceConstructor('foo, bar');
await waitForPromises();
expect(useSpy.mock.calls).toHaveLength(2);
expect(calls).toEqual(['use', 'use', 'event']);
});
}); });
describe('multiple instances', () => { describe('multiple instances', () => {
......
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