diff options
Diffstat (limited to 'spec/frontend/ide/lib/diff/controller_spec.js')
-rw-r--r-- | spec/frontend/ide/lib/diff/controller_spec.js | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/spec/frontend/ide/lib/diff/controller_spec.js b/spec/frontend/ide/lib/diff/controller_spec.js new file mode 100644 index 00000000000..0b33a4c6ad6 --- /dev/null +++ b/spec/frontend/ide/lib/diff/controller_spec.js @@ -0,0 +1,215 @@ +import { Range } from 'monaco-editor'; +import Editor from '~/ide/lib/editor'; +import ModelManager from '~/ide/lib/common/model_manager'; +import DecorationsController from '~/ide/lib/decorations/controller'; +import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller'; +import { computeDiff } from '~/ide/lib/diff/diff'; +import { file } from '../../helpers'; + +describe('Multi-file editor library dirty diff controller', () => { + let editorInstance; + let controller; + let modelManager; + let decorationsController; + let model; + + beforeEach(() => { + editorInstance = Editor.create(); + editorInstance.createInstance(document.createElement('div')); + + modelManager = new ModelManager(); + decorationsController = new DecorationsController(editorInstance); + + model = modelManager.addModel(file('path')); + + controller = new DirtyDiffController(modelManager, decorationsController); + }); + + afterEach(() => { + controller.dispose(); + model.dispose(); + decorationsController.dispose(); + editorInstance.dispose(); + }); + + describe('getDiffChangeType', () => { + ['added', 'removed', 'modified'].forEach(type => { + it(`returns ${type}`, () => { + const change = { + [type]: true, + }; + + expect(getDiffChangeType(change)).toBe(type); + }); + }); + }); + + describe('getDecorator', () => { + ['added', 'removed', 'modified'].forEach(type => { + it(`returns with linesDecorationsClassName for ${type}`, () => { + const change = { + [type]: true, + }; + + expect(getDecorator(change).options.linesDecorationsClassName).toBe( + `dirty-diff dirty-diff-${type}`, + ); + }); + + it('returns with line numbers', () => { + const change = { + lineNumber: 1, + endLineNumber: 2, + [type]: true, + }; + + const { range } = getDecorator(change); + + expect(range.startLineNumber).toBe(1); + expect(range.endLineNumber).toBe(2); + expect(range.startColumn).toBe(1); + expect(range.endColumn).toBe(1); + }); + }); + }); + + describe('attachModel', () => { + it('adds change event callback', () => { + jest.spyOn(model, 'onChange').mockImplementation(() => {}); + + controller.attachModel(model); + + expect(model.onChange).toHaveBeenCalled(); + }); + + it('adds dispose event callback', () => { + jest.spyOn(model, 'onDispose').mockImplementation(() => {}); + + controller.attachModel(model); + + expect(model.onDispose).toHaveBeenCalled(); + }); + + it('calls throttledComputeDiff on change', () => { + jest.spyOn(controller, 'throttledComputeDiff').mockImplementation(() => {}); + + controller.attachModel(model); + + model.getModel().setValue('123'); + + expect(controller.throttledComputeDiff).toHaveBeenCalled(); + }); + + it('caches model', () => { + controller.attachModel(model); + + expect(controller.models.has(model.url)).toBe(true); + }); + }); + + describe('computeDiff', () => { + it('posts to worker', () => { + jest.spyOn(controller.dirtyDiffWorker, 'postMessage').mockImplementation(() => {}); + + controller.computeDiff(model); + + expect(controller.dirtyDiffWorker.postMessage).toHaveBeenCalledWith({ + path: model.path, + originalContent: '', + newContent: '', + }); + }); + }); + + describe('reDecorate', () => { + it('calls computeDiff when no decorations are cached', () => { + jest.spyOn(controller, 'computeDiff').mockImplementation(() => {}); + + controller.reDecorate(model); + + expect(controller.computeDiff).toHaveBeenCalledWith(model); + }); + + it('calls decorate when decorations are cached', () => { + jest.spyOn(controller.decorationsController, 'decorate').mockImplementation(() => {}); + + controller.decorationsController.decorations.set(model.url, 'test'); + + controller.reDecorate(model); + + expect(controller.decorationsController.decorate).toHaveBeenCalledWith(model); + }); + }); + + describe('decorate', () => { + it('adds decorations into decorations controller', () => { + jest.spyOn(controller.decorationsController, 'addDecorations').mockImplementation(() => {}); + + controller.decorate({ data: { changes: [], path: model.path } }); + + expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith( + model, + 'dirtyDiff', + expect.anything(), + ); + }); + + it('adds decorations into editor', () => { + const spy = jest.spyOn(controller.decorationsController.editor.instance, 'deltaDecorations'); + + controller.decorate({ + data: { changes: computeDiff('123', '1234'), path: model.path }, + }); + + expect(spy).toHaveBeenCalledWith( + [], + [ + { + range: new Range(1, 1, 1, 1), + options: { + isWholeLine: true, + linesDecorationsClassName: 'dirty-diff dirty-diff-modified', + }, + }, + ], + ); + }); + }); + + describe('dispose', () => { + it('calls disposable dispose', () => { + jest.spyOn(controller.disposable, 'dispose'); + + controller.dispose(); + + expect(controller.disposable.dispose).toHaveBeenCalled(); + }); + + it('terminates worker', () => { + jest.spyOn(controller.dirtyDiffWorker, 'terminate'); + + controller.dispose(); + + expect(controller.dirtyDiffWorker.terminate).toHaveBeenCalled(); + }); + + it('removes worker event listener', () => { + jest.spyOn(controller.dirtyDiffWorker, 'removeEventListener'); + + controller.dispose(); + + expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith( + 'message', + expect.anything(), + ); + }); + + it('clears cached models', () => { + controller.attachModel(model); + + model.dispose(); + + expect(controller.models.size).toBe(0); + }); + }); +}); |