Commit 3c0010fd authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '232503-editor-lite-extensions-on-instance' into 'master'

Extends Editor Lite to support extensions and custom options to be passed directly to createInstance

See merge request gitlab-org/gitlab!44723
parents d762147b 9823fae9
......@@ -39,6 +39,26 @@ export default class Editor {
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.includes('/') ? '' : 'editor/';
const trimmedExt = ext.replace(/^\//, '').trim();
Editor.pushToImportsArray(promises, `~/${prefix}${trimmedExt}`);
});
return Promise.all(promises);
}
/**
* Creates a monaco instance with the given options.
*
......@@ -53,6 +73,7 @@ export default class Editor {
blobPath = '',
blobContent = '',
blobGlobalId = '',
extensions = [],
...instanceOptions
} = {}) {
if (!el) {
......@@ -80,6 +101,22 @@ export default class Editor {
model.dispose();
});
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);
});
}
})
.then(() => {
el.dispatchEvent(new Event('editor-ready'));
})
.catch(e => {
throw e;
});
this.instances.push(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 waitForPromises from 'helpers/wait_for_promises';
import Editor from '~/editor/editor_lite';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from '~/editor/constants';
......@@ -253,55 +254,125 @@ describe('Base editor', () => {
const MyExt3 = {
foo: foo2,
};
beforeEach(() => {
instance = editor.createInstance({ el: editorEl, blobPath, blobContent });
});
it('is extensible with the extensions', () => {
expect(instance.foo).toBeUndefined();
describe('basic functionality', () => {
beforeEach(() => {
instance = editor.createInstance({ el: editorEl, blobPath, blobContent });
});
editor.use(MyExt1);
expect(instance.foo).toEqual(foo1);
});
it('is extensible with the extensions', () => {
expect(instance.foo).toBeUndefined();
it('does not fail if no extensions supplied', () => {
const spy = jest.spyOn(global.console, 'error');
editor.use();
instance.use(MyExt1);
expect(instance.foo).toEqual(foo1);
});
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(instance.foo).toBeUndefined();
expect(instance.bar).toBeUndefined();
expect(spy).not.toHaveBeenCalled();
});
editor.use([MyExt1, MyExt2]);
it('is extensible with multiple extensions', () => {
expect(instance.foo).toBeUndefined();
expect(instance.bar).toBeUndefined();
expect(instance.foo).toEqual(foo1);
expect(instance.bar).toEqual(bar);
});
instance.use([MyExt1, MyExt2]);
it('uses the last definition of a method in case of an overlap', () => {
editor.use([MyExt1, MyExt2, MyExt3]);
expect(instance).toEqual(
expect.objectContaining({
foo: foo2,
bar,
}),
);
expect(instance.foo).toEqual(foo1);
expect(instance.bar).toEqual(bar);
});
it('uses the last definition of a method in case of an overlap', () => {
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', () => {
const FunctionExt = {
inst() {
return this;
},
mod() {
return this.getModel();
},
describe('extensions as an instance parameter', () => {
let editorExtensionSpy;
const instanceConstructor = (extensions = []) => {
return editor.createInstance({
el: editorEl,
blobPath,
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', () => {
......
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