Commit d6abbef4 authored by Denys Mishunov's avatar Denys Mishunov

Internal refactoring and testing Diff Editor

parent 8518a249
...@@ -34,15 +34,12 @@ export default class EditorLite { ...@@ -34,15 +34,12 @@ export default class EditorLite {
monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME); monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME);
} }
static updateModelLanguage(path, instance) { static getModelLanguage(path) {
if (!instance) return;
const model = instance.getModel();
const ext = `.${path.split('.').pop()}`; const ext = `.${path.split('.').pop()}`;
const language = monacoLanguages const language = monacoLanguages
.getLanguages() .getLanguages()
.find((lang) => lang.extensions.indexOf(ext) !== -1); .find((lang) => lang.extensions.indexOf(ext) !== -1);
const id = language ? language.id : 'plaintext'; return language ? language.id : 'plaintext';
monacoEditor.setModelLanguage(model, id);
} }
static pushToImportsArray(arr, toImport) { static pushToImportsArray(arr, toImport) {
...@@ -113,6 +110,7 @@ export default class EditorLite { ...@@ -113,6 +110,7 @@ export default class EditorLite {
blobOriginalContent, blobOriginalContent,
blobGlobalId, blobGlobalId,
instance, instance,
diff,
} = {}) { } = {}) {
if (!instance) { if (!instance) {
return null; return null;
...@@ -121,20 +119,28 @@ export default class EditorLite { ...@@ -121,20 +119,28 @@ export default class EditorLite {
const uri = Uri.file(uriFilePath); const uri = Uri.file(uriFilePath);
const existingModel = monacoEditor.getModel(uri); const existingModel = monacoEditor.getModel(uri);
const model = existingModel || monacoEditor.createModel(blobContent, undefined, uri); const model = existingModel || monacoEditor.createModel(blobContent, undefined, uri);
if (!blobOriginalContent) { if (!diff) {
instance.setModel(model); instance.setModel(model);
} else { return model;
instance.setModel({
original: monacoEditor.createModel(blobOriginalContent, undefined, uri),
modified: model,
});
} }
return instance.getModel(); const diffModel = {
original: monacoEditor.createModel(
blobOriginalContent,
EditorLite.getModelLanguage(model.uri.path),
),
modified: model,
};
instance.setModel(diffModel);
return diffModel;
} }
static decorateInstance = (inst) => { static decorateInstance = (inst) => {
const decoratedInstance = inst; const decoratedInstance = inst;
decoratedInstance.updateModelLanguage = (path) => EditorLite.updateModelLanguage(path, inst); decoratedInstance.updateModelLanguage = (path) => {
const lang = EditorLite.getModelLanguage(path);
const model = decoratedInstance.getModel();
return monacoEditor.setModelLanguage(model, lang);
};
decoratedInstance.use = (exts = []) => { decoratedInstance.use = (exts = []) => {
const extensions = Array.isArray(exts) ? exts : [exts]; const extensions = Array.isArray(exts) ? exts : [exts];
extensions.forEach((extension) => { extensions.forEach((extension) => {
...@@ -145,6 +151,26 @@ export default class EditorLite { ...@@ -145,6 +151,26 @@ export default class EditorLite {
return decoratedInstance; return decoratedInstance;
}; };
static onInstanceDisposal(editor, instance, model) {
const index = editor.instances.findIndex((inst) => inst === instance);
editor.instances.splice(index, 1);
const instanceModel = instance.getModel() || model;
if (!instanceModel) {
return;
}
if (instance.getEditorType() === EDITOR_TYPE_DIFF) {
const { original, modified } = instanceModel;
if (original) {
original.dispose();
}
if (modified) {
modified.dispose();
}
} else {
instanceModel.dispose();
}
}
/** /**
* Creates a monaco instance with the given options. * Creates a monaco instance with the given options.
* *
...@@ -182,28 +208,12 @@ export default class EditorLite { ...@@ -182,28 +208,12 @@ export default class EditorLite {
blobPath, blobPath,
blobContent, blobContent,
instance, instance,
diff,
}); });
} }
instance.onDidDispose(() => { instance.onDidDispose(() => {
const index = this.instances.findIndex((inst) => inst === instance); EditorLite.onInstanceDisposal(this, instance, model);
this.instances.splice(index, 1);
const instanceModel = instance.getModel();
if (instanceModel) {
if (instance.getEditorType() === EDITOR_TYPE_DIFF) {
const { original, modified } = instanceModel;
if (original) {
original.dispose();
}
if (modified) {
modified.dispose();
}
} else {
instanceModel.dispose();
}
} else if (model) {
model.dispose();
}
}); });
EditorLite.manageDefaultExtensions(instance, el, extensions); EditorLite.manageDefaultExtensions(instance, el, extensions);
...@@ -213,7 +223,7 @@ export default class EditorLite { ...@@ -213,7 +223,7 @@ export default class EditorLite {
} }
createDiffInstance(args) { createDiffInstance(args) {
this.createInstance({ return this.createInstance({
...args, ...args,
diff: true, diff: true,
}); });
......
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
EDITOR_LITE_INSTANCE_ERROR_NO_EL, EDITOR_LITE_INSTANCE_ERROR_NO_EL,
URI_PREFIX, URI_PREFIX,
EDITOR_READY_EVENT, EDITOR_READY_EVENT,
EDITOR_TYPE_DIFF,
} from '~/editor/constants'; } from '~/editor/constants';
describe('Base editor', () => { describe('Base editor', () => {
...@@ -18,7 +19,7 @@ describe('Base editor', () => { ...@@ -18,7 +19,7 @@ describe('Base editor', () => {
const blobContent = 'Foo Bar'; const blobContent = 'Foo Bar';
const blobPath = 'test.md'; const blobPath = 'test.md';
const blobGlobalId = 'snippet_777'; const blobGlobalId = 'snippet_777';
const fakeModel = { foo: 'bar', dispose: jest.fn() }; const fakeModel = { foo: 'bar', dispose: jest.fn(), uri: { path: blobPath } };
beforeEach(() => { beforeEach(() => {
setFixtures('<div id="editor" data-editor-loading></div>'); setFixtures('<div id="editor" data-editor-loading></div>');
...@@ -30,6 +31,9 @@ describe('Base editor', () => { ...@@ -30,6 +31,9 @@ describe('Base editor', () => {
afterEach(() => { afterEach(() => {
editor.dispose(); editor.dispose();
editorEl.remove(); editorEl.remove();
monacoEditor.getModels().forEach((model) => {
model.dispose();
});
}); });
const createUri = (...paths) => Uri.file([URI_PREFIX, ...paths].join('/')); const createUri = (...paths) => Uri.file([URI_PREFIX, ...paths].join('/'));
...@@ -53,6 +57,7 @@ describe('Base editor', () => { ...@@ -53,6 +57,7 @@ describe('Base editor', () => {
let getModel; let getModel;
let dispose; let dispose;
let modelsStorage; let modelsStorage;
let instanceCreateResponse;
beforeEach(() => { beforeEach(() => {
setModel = jest.fn(); setModel = jest.fn();
...@@ -60,21 +65,24 @@ describe('Base editor', () => { ...@@ -60,21 +65,24 @@ describe('Base editor', () => {
dispose = jest.fn(); dispose = jest.fn();
use = jest.fn(); use = jest.fn();
modelsStorage = new Map(); modelsStorage = new Map();
});
describe('instance of the Code Editor', () => {
beforeEach(() => {
modelSpy = jest.spyOn(monacoEditor, 'createModel').mockImplementation(() => fakeModel); modelSpy = jest.spyOn(monacoEditor, 'createModel').mockImplementation(() => fakeModel);
instanceSpy = jest.spyOn(monacoEditor, 'create').mockImplementation(() => ({ jest.spyOn(monacoEditor, 'getModel').mockImplementation((uri) => {
return modelsStorage.get(uri.path);
});
instanceCreateResponse = {
setModel, setModel,
getModel, getModel,
dispose, dispose,
use, use,
onDidDispose: jest.fn(), onDidDispose: jest.fn(),
})); };
jest.spyOn(monacoEditor, 'getModel').mockImplementation((uri) => {
return modelsStorage.get(uri.path);
}); });
describe('instance of the Code Editor', () => {
beforeEach(() => {
instanceSpy = jest
.spyOn(monacoEditor, 'create')
.mockImplementation(() => instanceCreateResponse);
}); });
it('throws an error if no dom element is supplied', () => { it('throws an error if no dom element is supplied', () => {
...@@ -111,7 +119,7 @@ describe('Base editor', () => { ...@@ -111,7 +119,7 @@ describe('Base editor', () => {
const a = editor.createInstance(defaultArguments); const a = editor.createInstance(defaultArguments);
const b = editor.createInstance(defaultArguments); const b = editor.createInstance(defaultArguments);
expect(a === b).toBe(false); expect(a).toBe(b);
expect(modelSpy).toHaveBeenCalledTimes(1); expect(modelSpy).toHaveBeenCalledTimes(1);
}); });
...@@ -146,7 +154,7 @@ describe('Base editor', () => { ...@@ -146,7 +154,7 @@ describe('Base editor', () => {
); );
}); });
it('disposes instance when the editor is disposed', () => { it('disposes instance when the global editor is disposed', () => {
editor.createInstance(defaultArguments); editor.createInstance(defaultArguments);
expect(dispose).not.toHaveBeenCalled(); expect(dispose).not.toHaveBeenCalled();
...@@ -155,21 +163,26 @@ describe('Base editor', () => { ...@@ -155,21 +163,26 @@ describe('Base editor', () => {
expect(dispose).toHaveBeenCalled(); expect(dispose).toHaveBeenCalled();
}); });
it("removes the disposed instance from the global editor's storage and disposes the associated model", () => {
instanceCreateResponse.getModel = jest.fn().mockReturnValue(fakeModel);
instanceCreateResponse.getEditorType = jest.fn().mockReturnValue('code');
editor.createInstance(defaultArguments);
expect(editor.instances).toHaveLength(1);
expect(fakeModel.dispose).not.toHaveBeenCalled();
EditorLite.onInstanceDisposal(editor, instanceCreateResponse);
expect(editor.instances).toHaveLength(0);
expect(fakeModel.dispose).toHaveBeenCalled();
});
}); });
describe('instance of the Diff Editor', () => { describe('instance of the Diff Editor', () => {
beforeEach(() => { beforeEach(() => {
modelSpy = jest.spyOn(monacoEditor, 'createModel').mockImplementation(() => fakeModel); instanceSpy = jest
instanceSpy = jest.spyOn(monacoEditor, 'createDiffEditor').mockImplementation(() => ({ .spyOn(monacoEditor, 'createDiffEditor')
setModel, .mockImplementation(() => instanceCreateResponse);
getModel,
dispose,
use,
onDidDispose: jest.fn(),
}));
jest.spyOn(monacoEditor, 'getModel').mockImplementation((uri) => {
return modelsStorage.get(uri.path);
});
}); });
it('Diff Editor goes through the normal path of Code Editor just with the flag ON', () => { it('Diff Editor goes through the normal path of Code Editor just with the flag ON', () => {
...@@ -197,12 +210,33 @@ describe('Base editor', () => { ...@@ -197,12 +210,33 @@ describe('Base editor', () => {
expect(modelSpy).toHaveBeenCalledTimes(2); expect(modelSpy).toHaveBeenCalledTimes(2);
expect(modelSpy.mock.calls[0]).toEqual([blobContent, undefined, uri]); expect(modelSpy.mock.calls[0]).toEqual([blobContent, undefined, uri]);
expect(modelSpy.mock.calls[1]).toEqual([blobOriginalContent, undefined, uri]); expect(modelSpy.mock.calls[1]).toEqual([blobOriginalContent, 'markdown']);
expect(setModel).toHaveBeenCalledWith({ expect(setModel).toHaveBeenCalledWith({
original: expect.anything(), original: expect.anything(),
modified: fakeModel, modified: fakeModel,
}); });
}); });
it('correctly disposes the diff editor model', () => {
const modifiedModel = fakeModel;
const originalModel = { ...fakeModel };
instanceCreateResponse.getModel = jest.fn().mockReturnValue({
original: originalModel,
modified: modifiedModel,
});
instanceCreateResponse.getEditorType = jest.fn().mockReturnValue(EDITOR_TYPE_DIFF);
editor.createDiffInstance({ ...defaultArguments, blobOriginalContent });
expect(editor.instances).toHaveLength(1);
expect(originalModel.dispose).not.toHaveBeenCalled();
expect(modifiedModel.dispose).not.toHaveBeenCalled();
EditorLite.onInstanceDisposal(editor, instanceCreateResponse);
expect(editor.instances).toHaveLength(0);
expect(originalModel.dispose).toHaveBeenCalled();
expect(modifiedModel.dispose).toHaveBeenCalled();
});
}); });
}); });
...@@ -293,6 +327,7 @@ describe('Base editor', () => { ...@@ -293,6 +327,7 @@ describe('Base editor', () => {
expect(monacoEditor.getModels()).toHaveLength(2); expect(monacoEditor.getModels()).toHaveLength(2);
inst1.dispose(); inst1.dispose();
expect(inst1.getModel()).toBe(null); expect(inst1.getModel()).toBe(null);
expect(inst2.getModel()).not.toBe(null); expect(inst2.getModel()).not.toBe(null);
expect(editor.instances).toHaveLength(1); expect(editor.instances).toHaveLength(1);
......
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