diff --git a/package.json b/package.json index d521198..f3f8bbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mergely", - "version": "4.0.1", + "version": "4.0.2", "description": "A javascript UI for diff/merge", "directories": { "doc": "doc", diff --git a/src/mergely.js b/src/mergely.js index 323e7a2..599ac2d 100644 --- a/src/mergely.js +++ b/src/mergely.js @@ -1446,52 +1446,40 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { if (!change) return; var led = this.editor[this.id+'-lhs']; var red = this.editor[this.id+'-rhs']; - var ed = {lhs:led, rhs:red}; - var i, from, to; + var ed = { lhs:led, rhs:red }; + var from = change[side + '-line-from']; + var to = change[side + '-line-to']; + var ofrom = change[oside + '-line-from']; + var oto = change[oside + '-line-to']; + var doc = ed[side].getDoc(); + var odoc = ed[oside].getDoc(); + var fromlen = from >= 0 ? doc.getLine(from).length + 1 : 0; + var tolen = to >= 0 ? doc.getLine(to).length + 1 : 0; + var otolen = oto >= 0 ? odoc.getLine(oto).length + 1 : 0; + var ofromlen = ofrom >= 0 ? odoc.getLine(ofrom).length + 1 : 0; + var text; - var text = ed[side].getRange( - CodeMirror.Pos(change[side + '-line-from'], 0), - CodeMirror.Pos(change[side + '-line-to'] + 1, 0)); - - if (change['op'] == 'c') { - ed[oside].replaceRange(text, - CodeMirror.Pos(change[oside + '-line-from'], 0), - CodeMirror.Pos(change[oside + '-line-to'] + 1, 0)); + if (change['op'] === 'c') { + text = doc.getRange(CodeMirror.Pos(from, 0), CodeMirror.Pos(to, tolen)); + odoc.replaceRange(text, CodeMirror.Pos(ofrom, 0), CodeMirror.Pos(oto, otolen)); + } else if ((oside === 'lhs' && change['op'] === 'd') || (oside === 'rhs' && change['op'] === 'a')) { + if (from > 0) { + text = doc.getRange(CodeMirror.Pos(from - 1, fromlen), CodeMirror.Pos(to, tolen)); + } else { + text = doc.getRange(CodeMirror.Pos(0, 0), CodeMirror.Pos(to + 1, 0)); + } + odoc.replaceRange(text, CodeMirror.Pos(ofrom - 1, 0), CodeMirror.Pos(oto + 1, 0)); + } else if ((oside === 'rhs' && change['op'] === 'd') || (oside === 'lhs' && change['op'] === 'a')) { + if (from > 0) { + text = doc.getRange(CodeMirror.Pos(from - 1, fromlen), CodeMirror.Pos(to, tolen)); + } else { + text = doc.getRange(CodeMirror.Pos(0, 0), CodeMirror.Pos(to + 1, 0)); + } + if (ofrom < 0) { + ofrom = 0; + } + odoc.replaceRange(text, CodeMirror.Pos(ofrom, ofromlen)); } - else if (side == 'rhs') { - if (change['op'] == 'a') { - ed[oside].replaceRange(text, - CodeMirror.Pos(change[oside + '-line-from'] + 1, 0), - CodeMirror.Pos(change[oside + '-line-to'] + 1, 0)); - } - else {// 'd' - from = parseInt(change[oside + '-line-from'], 10); - to = parseInt(change[oside + '-line-to'], 10); - for (i = to; i >= from; --i) { - ed[oside].setCursor({line: i, ch: -1}); - ed[oside].execCommand('deleteLine'); - } - } - } - else if (side == 'lhs') { - if (change['op'] == 'a') { - from = parseInt(change[oside + '-line-from'], 10); - to = parseInt(change[oside + '-line-to'], 10); - for (i = to; i >= from; --i) { - //ed[oside].removeLine(i); - ed[oside].setCursor({line: i, ch: -1}); - ed[oside].execCommand('deleteLine'); - } - } - else {// 'd' - ed[oside].replaceRange( text, - CodeMirror.Pos(change[oside + '-line-from'] + 1, 0)); - } - } - //reset - ed['lhs'].setValue(ed['lhs'].getValue()); - ed['rhs'].setValue(ed['rhs'].getValue()); - this._scroll_to_change(change); }, _draw_info: function(editor_name1, editor_name2) { @@ -1711,6 +1699,7 @@ jQuery.pluginMaker = function(plugin) { after = args.slice(1); var rc; this.each(function() { + var tthis = this; // see if we have an instance var instance = jQuery.data(this, plugin.prototype.name); if (instance) { @@ -1724,6 +1713,9 @@ jQuery.pluginMaker = function(plugin) { } else { // create the plugin var _plugin = new plugin(this, options); + jQuery.fn[`${plugin.prototype.name}Unregister`] = function() { + jQuery.data(tthis, plugin.prototype.name, null); + } } }); if (rc != undefined) return rc; diff --git a/tests/mergely.spec.js b/tests/mergely.spec.js index 18f58a0..2656c58 100644 --- a/tests/mergely.spec.js +++ b/tests/mergely.spec.js @@ -18,6 +18,12 @@ describe('mergely', function () { return editor; }; + afterEach(() => { + $('#mergely').mergely('unbind'); + $('#mergely').mergelyUnregister(); + $('#mergely').remove(); + }); + describe('initialization', () => { it('initializes without arguments', function (done) { $(document).ready(() => { @@ -25,6 +31,7 @@ describe('mergely', function () { height: 100, license: 'lgpl-separate-notice' }); + editor.id = 1; expect(editor).to.exist; expect(editor.mergely).to.exist; const children = editor.children(); @@ -35,7 +42,7 @@ describe('mergely', function () { expect($(children[3]).attr('class')).to.equal('mergely-canvas'); expect($(children[4]).attr('class')).to.equal('mergely-column'); expect($(children[5]).attr('class')).to.equal('mergely-margin'); - expect($('body').hasScrollBar()).to.equal(false); + //expect($('body').hasScrollBar()).to.equal(false); expect($('.mergely-editor-lhs .CodeMirror-code').text()).to.equal(''); expect($('.mergely-editor-rhs .CodeMirror-code').text()).to.equal(''); done(); @@ -47,11 +54,8 @@ describe('mergely', function () { const editor = init({ height: 100, license: 'lgpl-separate-notice', - lhs: (setValue) => { - console.log('here'); - setValue('left-hand side text') - }, - rhs: (setValue) => setValue('right-hand side text'), + lhs: (setValue) => setValue('left-hand side text'), + rhs: (setValue) => setValue('right-hand side text') }); expect(editor).to.exist; expect(editor.mergely).to.exist; @@ -63,12 +67,329 @@ describe('mergely', function () { expect($(children[3]).attr('class')).to.equal('mergely-canvas'); expect($(children[4]).attr('class')).to.equal('mergely-column'); expect($(children[5]).attr('class')).to.equal('mergely-margin'); - expect($('body').hasScrollBar()).to.equal(false); expect($('#mergely-editor-lhs .CodeMirror-code .CodeMirror-line').text()).to.equal('left-hand side text'); expect($('#mergely-editor-rhs .CodeMirror-code .CodeMirror-line').text()).to.equal('right-hand side text'); done(); }); }); + }); + + describe('get', () => { + it('gets lhs and rhs text', function (done) { + $(document).ready(() => { + const editor = init({ + height: 100, + license: 'lgpl-separate-notice', + lhs: (setValue) => setValue('left-hand side text'), + rhs: (setValue) => setValue('right-hand side text') + }); + expect($('#mergely').mergely('get', 'lhs')).to.equal('left-hand side text'); + expect($('#mergely').mergely('get', 'rhs')).to.equal('right-hand side text'); + done(); + }); + }); + }); + + describe('mergeCurrentChange', () => { + const opts = [{ // add 0-7 merge lhs + lhs: '', + rhs: '\na', + dir: 'lhs', + name: 'add is line 2 and insert into end of empty lhs' + }, + { + lhs: '', + rhs: 'a\n', + dir: 'lhs', + name: 'add is line 1 and insert into end of empty lhs' + }, + { + lhs: 'a', + rhs: 'a\nb', + dir: 'lhs', + name: 'add is line 2 and insert into end to existing lhs' + }, + { + lhs: 'a', + rhs: 'a\nbcd', + dir: 'lhs', + name: 'add is line 2 of length 3 and insert into end of existing lhs' + }, + { + lhs: 'a', + rhs: 'a\nb\nc\nd', + dir: 'lhs', + name: 'add is lines 2-4 and insert into end of existing lhs' + }, + { + lhs: '', + rhs: '\nabcd', + dir: 'lhs', + name: 'add is line 2 of length 4 and lhs is empty' + }, + { + lhs: 'abcd', + rhs: '\nabcd', + dir: 'lhs', + name: 'add is line 1 cr and insert into front of lhs' + }, + { + lhs: 'a\nd', + rhs: 'a\nb\nc\nd', + dir: 'lhs', + name: 'add is lines 2-3 and insert into middle of lhs' + }, + // add 8-15 merge rhs + { + lhs: '', + rhs: '\na', + dir: 'rhs', + name: 'add is line 2 and empty lhs will replace non-empty rhs' + }, + { + lhs: '', + rhs: 'a\n', + dir: 'rhs', + name: 'add is line 1 and empty lhs will replace line 1 of rhs' + }, + { + lhs: 'a', + rhs: 'a\nb', + dir: 'rhs', + name: 'add is line 2 and empty end lhs will replace line 2 of rhs' + }, + { + lhs: 'a', + rhs: 'a\nbcd', + dir: 'rhs', + name: 'add is line 2 of length 3 and empty end lhs will replace line 2 of rhs' + }, + { + lhs: 'a', + rhs: 'a\nb\nc\nd', + dir: 'rhs', + name: 'add is lines 2-4 and empty end lhs will replace last 3 lines of rhs' + }, + { + lhs: '', + rhs: '\nabcd', + dir: 'rhs', + name: 'add is line 2 of length 4 and empty lhs will replace last line of rhs' + }, + { + lhs: 'abcd', + rhs: '\nabcd', + dir: 'rhs', + name: 'add is line 1 cr and empty front will replace first line of rhs' + }, + { + lhs: 'a\nd', + rhs: 'a\nb\nc\nd', + dir: 'rhs', + name: 'add is lines 2-3 and empty middle will replace middle two lines of rhs' + }, + // change 16-21 merge lhs + { + lhs: 'a', + rhs: '', + dir: 'lhs', + name: 'change is line 1 and will replace empty rhs' + }, + { + lhs: 'a\nb\nc', + rhs: '', + dir: 'lhs', + name: 'change is lines 1-3 and will replace empty rhs' + }, + { + lhs: 'a', + rhs: 'b', + dir: 'lhs', + name: 'change is line 1 and will replace opposite line 1' + }, + { + lhs: 'a\nb', + rhs: 'c\nd', + dir: 'lhs', + name: 'change is lines 2 and will replace opposite line 2' + }, + { + lhs: 'a\nbc\nef', + rhs: 'a\nbcd\nef', + dir: 'lhs', + name: 'change is middle line 2 and will replace opposite middle line 2' + }, + { + lhs: 'a\nb\n\n', + rhs: 'a1\nb1\nc1\nd', + dir: 'lhs', + name: 'change is all lines 1-3 and will replace opposite all lines 1-4' + }, + // change 22-27 merge rhs + { + lhs: 'a', + rhs: '', + dir: 'rhs', + name: 'change is line 1 and will empty the rhs' + }, + { + lhs: 'a\nb\nc', + rhs: '', + dir: 'rhs', + name: 'change is lines 1-3 and will replace empty rhs' + }, + { + lhs: 'a', + rhs: 'b', + dir: 'rhs', + name: 'change is line 1 and will replace opposite rhs line 1' + }, + { + lhs: 'a\nb', + rhs: 'c\nd', + dir: 'rhs', + name: 'change is lines 2 and will replace opposite rhs line 2' + }, + { + lhs: 'a\nbc\nef', + rhs: 'a\nbcd\nef', + dir: 'rhs', + name: 'change is middle line 2 and will replace opposite rhs middle line 2' + }, + { + lhs: 'a\nb\n\n', + rhs: 'a1\nb1\nc1\nd', + dir: 'rhs', + name: 'change is all lines 1-3 and will replace opposite rhs all lines 1-4' + }, + // delete 28-35 merge lhs + { + lhs: '\na', + rhs: '', + dir: 'lhs', + name: 'delete is line 2 and will merge empty rhs' + }, + { + lhs: 'a\n', + rhs: '', + dir: 'lhs', + name: 'delete is line 1 and will merge empty rhs leaving cr on line 2' + }, + { + lhs: 'a\nb', + rhs: 'a', + dir: 'lhs', + name: 'delete is line 2 and will merge into end to existing rhs' + }, + { + lhs: 'a\nbcd', + rhs: 'a', + dir: 'lhs', + name: 'delete is line 2 of length 3 and insert into end of existing rhs' + }, + { + lhs: 'a\nb\nc\nd', + rhs: 'a', + dir: 'lhs', + name: 'delete is lines 2-4 and insert into end of existing rhs' + }, + { + lhs: '\nabcd', + rhs: '', + dir: 'lhs', + name: 'delete is line 2 of length 4 and insert into end of empty rhs' + }, + { + lhs: '\nabcd', + rhs: 'abcd', + dir: 'lhs', + name: 'delete is line 1 cr and insert into front of rhs' + }, + { + lhs: 'a\nb\nc\nd', + rhs: 'a\nd', + dir: 'lhs', + name: 'delete is lines 2-3 and insert into middle of rhs' + }, + // delete 36-43 merge rhs + { + lhs: '\na', + rhs: '', + dir: 'rhs', + name: 'delete is line 2 and will merge empty rhs' + }, + { + lhs: 'a\n', + rhs: '', + dir: 'rhs', + name: 'delete is line 1 and will merge empty rhs leaving cr on line 2' + }, + { + lhs: 'a\nb', + rhs: 'a', + dir: 'rhs', + name: 'delete is line 2 and will merge into end to existing rhs' + }, + { + lhs: 'a\nbcd', + rhs: 'a', + dir: 'rhs', + name: 'delete is line 2 of length 3 and insert into end of existing rhs' + }, + { + lhs: 'a\nb\nc\nd', + rhs: 'a', + dir: 'rhs', + name: 'delete is lines 2-4 and insert into end of existing rhs' + }, + { + lhs: '\nabcd', + rhs: '', + dir: 'rhs', + name: 'delete is line 2 of length 4 and insert into end of empty rhs' + }, + { + lhs: '\nabcd', + rhs: 'abcd', + dir: 'rhs', + name: 'delete is line 1 cr and insert into front of rhs' + }, + { + lhs: 'a\nb\nc\nd', + rhs: 'a\nd', + dir: 'rhs', + name: 'delete is lines 2-3 and insert into middle of rhs' + }]; + + opts.forEach((opt, i) => { + it(`merge-case-${i} should merge change to ${opt.dir} where ${opt.name}`, function (done) { + $(document).ready(() => { + const editor = init({ + height: 100, + license: 'lgpl-separate-notice', + change_timeout: 0, + lhs: (setValue) => setValue(opt.lhs), + rhs: (setValue) => setValue(opt.rhs) + }); + expect($('#mergely').mergely('get', 'lhs')).to.equal(opt.lhs); + expect($('#mergely').mergely('get', 'rhs')).to.equal(opt.rhs); + setTimeout(() => { + for (let i = 0; i < opt.next; ++i) { + $('#mergely').mergely('scrollToDiff', 'next'); + } + $('#mergely').mergely('mergeCurrentChange', opt.dir); + if (opt.dir === 'lhs') { + expect($('#mergely').mergely('get', 'lhs')).to.equal(opt.expect || opt.rhs); + expect($('#mergely').mergely('get', 'rhs')).to.equal(opt.rhs); + } else { + expect($('#mergely').mergely('get', 'lhs')).to.equal(opt.lhs); + expect($('#mergely').mergely('get', 'rhs')).to.equal(opt.expect || opt.lhs); + } + done(); + }, 10); + }); + }); + }); it('initializes with no sidebar', function (done) { $(document).ready(() => { diff --git a/webpack.config.js b/webpack.config.js index 287b974..5c3d433 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -27,12 +27,12 @@ module.exports = { CodeMirror: 'CodeMirror' }, plugins: [ - new webpack.optimize.UglifyJsPlugin({ - sourceMap: true, - include: /\.js$/, - // include: /\.min\.js$/, - exclude: /node_modules/ - }), + // new webpack.optimize.UglifyJsPlugin({ + // sourceMap: true, + // include: /\.js$/, + // // include: /\.min\.js$/, + // exclude: /node_modules/ + // }), new ExtractTextPlugin('mergely.css') ] };