1
0
mirror of synced 2025-12-24 16:38:25 +08:00

Compare commits

..

5 Commits
4.0.2 ... 4.0.6

Author SHA1 Message Date
Jamie Peabody
b717abdd1e 4.0.6 2018-07-17 21:41:59 +01:00
Jamie Peabody
d2b38249ae patch(fix-89): fixes missing merge buttons 2018-07-17 21:41:07 +01:00
Jamie Peabody
217674cd07 patch(fix #85): better XSS fix (#87)
* patch(fix #85): fixes xss vulnerability

* patch(fix #85): better XSS fix
2018-06-23 03:36:38 -07:00
Jamie Peabody
3812dd6026 patch(fix #85): fixes xss vulnerability (#86) 2018-06-21 14:04:36 -07:00
Jamie Peabody
ead86867c4 fix(issue-83): poor rendering performance 2018-06-17 13:38:52 +01:00
6 changed files with 3701 additions and 90 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/node_modules
/lib
mergely-*.tgz
package-lock.json

View File

@@ -1,5 +1,17 @@
# Changes
## 4.0.6
* #89: fixes missing merge buttons
## 4.0.5
* #85: fixes XSS vulnerability with DOM id
## 4.0.2
* #83: fixes poor rendering performance
## 4.0.0
### Breaking changes

2
package.json Normal file → Executable file
View File

@@ -1,6 +1,6 @@
{
"name": "mergely",
"version": "4.0.2",
"version": "4.0.6",
"description": "A javascript UI for diff/merge",
"directories": {
"doc": "doc",

View File

@@ -54,8 +54,8 @@ Mgly.sizeOf = function(obj) {
};
Mgly.LCS = function(x, y) {
this.x = x.replace(/[ ]{1}/g, '\n');
this.y = y.replace(/[ ]{1}/g, '\n');
this.x = (x && x.replace(/[ ]{1}/g, '\n')) || '';
this.y = (y && y.replace(/[ ]{1}/g, '\n')) || '';
};
jQuery.extend(Mgly.LCS.prototype, {
@@ -461,11 +461,11 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
lineNumbers: this.settings.line_numbers
};
var lhs_gutters = [];
if (this.lhs_cmsettings.line_numbers) {
if (this.lhs_cmsettings.lineNumbers) {
lhs_gutters = ['merge', 'CodeMirror-linenumbers']
}
var rhs_gutters = [];
if (this.rhs_cmsettings.line_numbers) {
if (this.rhs_cmsettings.lineNumbers) {
rhs_gutters = ['merge', 'CodeMirror-linenumbers']
}
jQuery.extend(true, this.lhs_cmsettings, this.settings.cmsettings, { gutters: lhs_gutters }, this.settings);
@@ -659,6 +659,13 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
bind: function(el) {
this.element.hide();
this.id = jQuery(el).attr('id');
try {
// ensure the id is valid for jQuery
jQuery(`#${this.id}`);
} catch (ex) {
console.error(`jQuery failed to find mergely: #${this.id}`);
return;
}
this.changed_timeout = null;
this.chfns = {};
this.chfns[this.id + '-lhs'] = [];
@@ -676,7 +683,7 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
}
else {
// homebrew
var style = 'opacity:0.4;width:10px;height:15px;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid #222;margin-right:5px;margin-top: -2px;';
var style = 'opacity:0.6;height:16px;background-color:#bfbfbf;cursor:pointer;text-align:center;color:#eee;border:1px solid #848484;margin-right:5px;margin-top:-2px;';
merge_lhs_button = '<div style="' + style + '" title="Merge left">&lt;</div>';
merge_rhs_button = '<div style="' + style + '" title="Merge right">&gt;</div>';
}
@@ -686,17 +693,32 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
// create the textarea and canvas elements
var height = '10px';
var width = '10px';
this.element.append(jQuery('<div id="mergely-splash">'));
this.element.append(jQuery('<div class="mergely-margin" style="height: ' + height + '"><canvas id="' + this.id + '-lhs-margin" width="8px" height="' + height + '"></canvas></div>'));
this.element.append(jQuery('<div style="position:relative;width:' + width + '; height:' + height + '" id="' + this.id + '-editor-lhs" class="mergely-column"><textarea style="" id="' + this.id + '-lhs"></textarea></div>'));
this.element.append(jQuery('<div class="mergely-canvas" style="height: ' + height + '"><canvas id="' + this.id + '-lhs-' + this.id + '-rhs-canvas" style="width:28px" width="28px" height="' + height + '"></canvas></div>'));
var rmargin = jQuery('<div class="mergely-margin" style="height: ' + height + '"><canvas id="' + this.id + '-rhs-margin" width="8px" height="' + height + '"></canvas></div>');
var splash = jQuery('<div id="mergely-splash">');
var canvasLhs = jQuery(`<div class="mergely-margin" style="height: '${height}'"><canvas id="lhs-margin" width="8px" height="'${height}'"></canvas></div>`);
canvasLhs.find('#lhs-margin').attr('id', `${this.id}-lhs-margin`);
var editorLhs = jQuery(`<div style="position:relative;width:'${width}'; height:'${height}'" id="editor-lhs" class="mergely-column"><textarea id="text-lhs"></textarea></div>`);
editorLhs.eq(0).attr('id', `${this.id}-editor-lhs`);
editorLhs.find('#text-lhs').attr('id', `${this.id}-lhs`);
var canvasMid = jQuery(`<div class="mergely-canvas" style="height: '${height}'"><canvas id="lhs-rhs-canvas" style="width:28px" width="28px" height="'${height}'"></canvas></div>`);
canvasMid.find('#mergely-canvas').attr('id', `${this.id}-mergely-canvas`);
canvasMid.find('#lhs-rhs-canvas').attr('id', `${this.id}-lhs-${this.id}-rhs-canvas`);
this.element.append(splash);
this.element.append(canvasLhs);
this.element.append(editorLhs);
this.element.append(canvasMid);
var canvasRhs = jQuery(`<div class="mergely-margin" style="height: '${height}'"><canvas id="rhs-margin" width="8px" height="'${height}'"></canvas></div>`);
canvasRhs.find('#rhs-margin').attr('id', `${this.id}-rhs-margin`);
if (this.settings.rhs_margin == 'left') {
this.element.append(rmargin);
this.element.append(canvasRhs);
}
this.element.append(jQuery('<div style="width:' + width + '; height:' + height + '" id="' + this.id + '-editor-rhs" class="mergely-column"><textarea style="" id="' + this.id + '-rhs"></textarea></div>'));
var editorRhs = jQuery(`<div style="width:'${width}'; height:'${height}'" id="editor-rhs" class="mergely-column"><textarea id="text-rhs"></textarea></div>`);
editorRhs.eq(0).attr('id', `${this.id}-editor-rhs`);
editorRhs.find('#text-rhs').attr('id', `${this.id}-rhs`);
this.element.append(editorRhs);
if (this.settings.rhs_margin != 'left') {
this.element.append(rmargin);
this.element.append(canvasRhs);
}
if (!this.settings.sidebar) {
this.element.find('.mergely-margin').css({display: 'none'});
@@ -734,32 +756,42 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
});
}
// check initialization
var rhstx;
try {
rhstx = this.element.find(`#${this.id}-rhs`).get(0);
} catch (ex) {
}
if (!rhstx) {
console.error('rhs textarea not defined - Mergely not initialized properly');
return;
}
var lhstx;
try {
lhstx = this.element.find(`#${this.id}-lhs`).get(0);
} catch (ex) {
}
if (!lhstx) {
console.error('lhs textarea not defined - Mergely not initialized properly');
return;
}
// get current diff border color
var color = jQuery('<div style="display:none" class="mergely current start" />').appendTo('body').css('border-top-color');
this.current_diff_color = color;
// codemirror
var cmstyle = '#' + this.id + ' .CodeMirror-gutter-text { padding: 5px 0 0 0; }' +
'#' + this.id + ' .CodeMirror-lines pre, ' + '#' + this.id + ' .CodeMirror-gutter-text pre { line-height: 18px; }' +
'.CodeMirror-linewidget { overflow: hidden; };';
var cmstyle = `#${this.id} .CodeMirror-gutter-text { padding: 5px 0 0 0; }
'#${this.id} .CodeMirror-lines pre, #${this.id} .CodeMirror-gutter-text pre { line-height: 18px; }
'.CodeMirror-linewidget { overflow: hidden; };`;
if (this.settings.autoresize) {
cmstyle += this.id + ' .CodeMirror-scroll { height: 100%; overflow: auto; }';
cmstyle += `${this.id} .CodeMirror-scroll { height: 100%; overflow: auto; }`;
}
// adjust the margin line height
cmstyle += '\n.CodeMirror { line-height: 18px; }';
jQuery('<style type="text/css">' + cmstyle + '</style>').appendTo('head');
jQuery(`<style type="text/css">${cmstyle}</style>`).appendTo('head');
//bind
var rhstx = this.element.find('#' + this.id + '-rhs').get(0);
if (!rhstx) {
console.error('rhs textarea not defined - Mergely not initialized properly');
return;
}
var lhstx = this.element.find('#' + this.id + '-lhs').get(0);
if (!rhstx) {
console.error('lhs textarea not defined - Mergely not initialized properly');
return;
}
// bind
var self = this;
this.editor = [];
this.editor[this.id + '-lhs'] = CodeMirror.fromTextArea(lhstx, this.lhs_cmsettings);
@@ -1046,21 +1078,13 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
}
return changes;
},
_get_viewport: function(editor_name1, editor_name2) {
var lhsvp = this.editor[editor_name1].getViewport();
var rhsvp = this.editor[editor_name2].getViewport();
return {from: Math.min(lhsvp.from, rhsvp.from), to: Math.max(lhsvp.to, rhsvp.to)};
_get_viewport_side: function(editor_name) {
return this.editor[editor_name].getViewport();
},
_is_change_in_view: function(vp, change) {
if (!this.settings.viewport) return true;
if ((change['lhs-line-from'] < vp.from && change['lhs-line-to'] < vp.to) ||
(change['lhs-line-from'] > vp.from && change['lhs-line-to'] > vp.to) ||
(change['rhs-line-from'] < vp.from && change['rhs-line-to'] < vp.to) ||
(change['rhs-line-from'] > vp.from && change['rhs-line-to'] > vp.to)) {
// if the change is outside the viewport, skip
return false;
}
return true;
_is_change_in_view: function(side, vp, change) {
return (change[`${side}-line-from`] >= vp.from && change[`${side}-line-from`] <= vp.to) ||
(change[`${side}-line-to`] >= vp.from && change[`${side}-line-to`] <= vp.to) ||
(vp.from >= change[`${side}-line-from`] && vp.to <= change[`${side}-line-to`]);
},
_set_top_offset: function (editor_name1) {
// save the current scroll position of the editor
@@ -1105,12 +1129,15 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
}
var lhschc = this.editor[editor_name1].charCoords({line: 0});
var rhschc = this.editor[editor_name2].charCoords({line: 0});
var vp = this._get_viewport(editor_name1, editor_name2);
var lhsvp = this._get_viewport_side(editor_name1);
var rhsvp = this._get_viewport_side(editor_name2);
for (var i = 0; i < changes.length; ++i) {
var change = changes[i];
if (!this.settings.sidebar && !this._is_change_in_view(vp, change)) {
if (this.settings.viewport &&
!this._is_change_in_view(lhsvp, 'lhs', change) &&
!this._is_change_in_view(lhsvp, 'rhs', change)) {
// if the change is outside the viewport, skip
delete change['lhs-y-start'];
delete change['lhs-y-end'];
@@ -1205,11 +1232,18 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
var led = this.editor[editor_name1];
var red = this.editor[editor_name2];
var current_diff = this._current_diff;
var lhsvp = this._get_viewport_side(editor_name1);
var rhsvp = this._get_viewport_side(editor_name2);
var timer = new Mgly.Timer();
led.operation(function() {
for (var i = 0; i < changes.length; ++i) {
var change = changes[i];
if (!this._is_change_in_view('lhs', lhsvp, change)) {
// if the change is outside the viewport, skip
continue;
}
var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
@@ -1252,24 +1286,22 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
led.setGutterMarker(llf, 'merge', rhs_button.get(0));
}
}
});
var vp = this._get_viewport(editor_name1, editor_name2);
}.bind(this));
this.trace('change', 'markup lhs-editor time', timer.stop());
red.operation(function() {
for (var i = 0; i < changes.length; ++i) {
var change = changes[i];
if (!this._is_change_in_view('rhs', rhsvp, change)) {
// if the change is outside the viewport, skip
continue;
}
var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
if (!self._is_change_in_view(vp, change)) {
// if the change is outside the viewport, skip
continue;
}
var clazz = ['mergely', 'rhs', change['op'], 'cid-' + i];
red.addLineClass(rlf, 'background', 'start');
red.addLineClass(rlt, 'background', 'end');
@@ -1307,7 +1339,7 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
red.setGutterMarker(rlf, 'merge', lhs_button.get(0));
}
}
});
}.bind(this));
this.trace('change', 'markup rhs-editor time', timer.stop());
// mark text deleted, LCS changes
@@ -1319,17 +1351,17 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
if (!this._is_change_in_view(vp, change)) {
// if the change is outside the viewport, skip
continue;
}
if (change['op'] == 'd') {
// apply delete to cross-out (left-hand side only)
var from = llf;
var to = llt;
var to_ln = led.lineInfo(to);
if (to_ln) {
marktext.push([led, {line:from, ch:0}, {line:to, ch:to_ln.text.length}, {className: 'mergely ch d lhs'}]);
if (this._is_change_in_view('lhs', lhsvp, change)) {
// the change is within the viewport
var to_ln = led.lineInfo(to);
if (to_ln) {
marktext.push([led, {line:from, ch:0}, {line:to, ch:to_ln.text.length}, {className: 'mergely ch d lhs'}]);
}
}
}
else if (change['op'] == 'c') {
@@ -1338,13 +1370,13 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
((j >= 0) && (j <= llt)) || ((k >= 0) && (k <= rlt));
++j, ++k) {
var lhs_line, rhs_line;
if (k + p > rlt) {
if (k + p > rlt && this._is_change_in_view('lhs', lhsvp, change)) {
// lhs continues past rhs, mark lhs as deleted
lhs_line = led.getLine( j );
marktext.push([led, {line:j, ch:0}, {line:j, ch:lhs_line.length}, {className: 'mergely ch d lhs'}]);
continue;
}
if (j + p > llt) {
if (j + p > llt && this._is_change_in_view('rhs', rhsvp, change)) {
// rhs continues past lhs, mark rhs as added
rhs_line = red.getLine( k );
marktext.push([red, {line:k, ch:0}, {line:k, ch:rhs_line.length}, {className: 'mergely ch a rhs'}]);
@@ -1355,10 +1387,14 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
var lcs = new Mgly.LCS(lhs_line, rhs_line);
lcs.diff(
function added (from, to) {
marktext.push([red, {line:k, ch:from}, {line:k, ch:to}, {className: 'mergely ch a rhs'}]);
if (self._is_change_in_view('rhs', rhsvp, change)) {
marktext.push([red, {line:k, ch:from}, {line:k, ch:to}, {className: 'mergely ch a rhs'}]);
}
},
function removed (from, to) {
marktext.push([led, {line:j, ch:from}, {line:j, ch:to}, {className: 'mergely ch d lhs'}]);
if (self._is_change_in_view('lhs', lhsvp, change)) {
marktext.push([led, {line:j, ch:from}, {line:j, ch:to}, {className: 'mergely ch d lhs'}]);
}
}
);
}
@@ -1413,29 +1449,37 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
return false;
});
// gutter markup
var lhsLineNumbers = jQuery('#mergely-lhs ~ .CodeMirror').find('.CodeMirror-linenumber');
var rhsLineNumbers = jQuery('#mergely-rhs ~ .CodeMirror').find('.CodeMirror-linenumber');
// gutter markup that highlights all gutter line numbers for the current change.
// cm doesn't give us the ability to style the line numbers directly.
var lhsLineNumbers = jQuery('#mergely-lhs ~ .CodeMirror .CodeMirror-code .CodeMirror-linenumber.CodeMirror-gutter-elt');
var rhsLineNumbers = jQuery('#mergely-rhs ~ .CodeMirror .CodeMirror-code .CodeMirror-linenumber.CodeMirror-gutter-elt');
var jf, jt, i, j;
rhsLineNumbers.removeClass('mergely current');
lhsLineNumbers.removeClass('mergely current');
for (var i = 0; i < changes.length; ++i) {
if (current_diff == i && change.op !== 'd') {
var change = changes[i];
var j, jf = change['rhs-line-from'], jt = change['rhs-line-to'] + 1;
for (j = jf; j < jt; j++) {
var n = (j + 1).toString();
rhsLineNumbers
.filter(function(i, node) { return jQuery(node).text() === n; })
.addClass('mergely current');
var lhsvpFrom = parseInt(lhsLineNumbers.eq(0).text(), 10) - 1;
var lhsvpTo = parseInt(lhsLineNumbers.eq(lhsLineNumbers.length - 1).text(), 10);
var rhsvpFrom = parseInt(rhsLineNumbers.eq(0).text(), 10) - 1;
var rhsvpTo = parseInt(rhsLineNumbers.eq(rhsLineNumbers.length - 1).text(), 10);
for (i = 0; i < changes.length; ++i) {
change = changes[i];
if (current_diff == i && change.op !== 'a') {
jf = change['lhs-line-from']
jt = change['lhs-line-to'] + 1;
for (j = jf; j < jt; ++j) {
if (j >= lhsvpFrom && j <= lhsvpTo) {
lhsLineNumbers.eq(j - lhsvpFrom).addClass('mergely current');
}
}
}
if (current_diff == i && change.op !== 'a') {
var change = changes[i];
jf = change['lhs-line-from'], jt = change['lhs-line-to'] + 1;
for (j = jf; j < jt; j++) {
var n = (j + 1).toString();
lhsLineNumbers.filter(function(i, node) { return jQuery(node).text() === n; })
.addClass('mergely current');
if (current_diff == i && change.op !== 'd') {
jf = change['rhs-line-from']
jt = change['rhs-line-to'] + 1;
for (j = jf; j < jt; ++j) {
if (j >= rhsvpFrom && j <= rhsvpTo) {
rhsLineNumbers.eq(j - rhsvpFrom).addClass('mergely current');
}
}
}
}
@@ -1538,7 +1582,9 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
ctx_rhs.fillRect(0, 0, 6.5, ex.visible_page_height);
ctx_rhs.strokeRect(0, 0, 6.5, ex.visible_page_height);
var vp = this._get_viewport(editor_name1, editor_name2);
var lhsvp = this._get_viewport_side(editor_name1);
var rhsvp = this._get_viewport_side(editor_name2);
for (var i = 0; i < changes.length; ++i) {
var change = changes[i];
var fill = this.settings.fgcolor[change['op']];
@@ -1568,7 +1614,9 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
ctx_rhs.fillRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5));
ctx_rhs.strokeRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5));
if (!this._is_change_in_view(vp, change)) {
if (!this._is_change_in_view('lhs', lhsvp, change) &&
!this._is_change_in_view('rhs', rhsvp, change)) {
// if the change is outside the viewport, skip
continue;
}

3412
tests/data/macbeth.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
const jQuery = require('jquery');
const CodeMirror = require('CodeMirror');
const mergely = require('../src/mergely');
const macbeth = require('./data/macbeth').join('\n');
const $ = jQuery;
(function($) {
@@ -19,9 +20,10 @@ describe('mergely', function () {
};
afterEach(() => {
$('#mergely').mergely('unbind');
$('#mergely').mergelyUnregister();
$('#mergely').remove();
const mergely = $('#mergely');
mergely.mergely('unbind');
mergely.mergelyUnregister();
mergely.remove();
});
describe('initialization', () => {
@@ -400,6 +402,7 @@ describe('mergely', function () {
for (let i = 0; i < opt.next; ++i) {
$('#mergely').mergely('scrollToDiff', 'next');
}
expect($('.merge-button').length > 0).to.be.true;
$('#mergely').mergely('mergeCurrentChange', opt.dir);
if (opt.dir === 'lhs') {
expect($('#mergely').mergely('get', 'lhs')).to.equal(opt.expect || opt.rhs);
@@ -414,4 +417,139 @@ describe('mergely', function () {
});
});
});
describe('_is_change_in_view', () => {
it('should be false when change less-than viewport', function (done) {
$(document).ready(() => {
const editor = init({
height: 100,
viewport: true,
license: 'lgpl-separate-notice',
lhs: (setValue) => setValue(macbeth),
rhs: (setValue) => setValue(macbeth)
});
const { mergely } = $('#mergely');
expect($('#mergely').mergely('_is_change_in_view', 'lhs', {from: 10, to: 20}, {
'lhs-line-from': 0,
'lhs-line-to': 9
})).to.be.false;
done();
});
});
it('should be true when change less-than-equal viewport', function (done) {
$(document).ready(() => {
const editor = init({
height: 100,
viewport: true,
license: 'lgpl-separate-notice',
lhs: (setValue) => setValue(macbeth),
rhs: (setValue) => setValue(macbeth)
});
const { mergely } = $('#mergely');
expect($('#mergely').mergely('_is_change_in_view', 'lhs', {from: 10, to: 20}, {
'lhs-line-from': 0,
'lhs-line-to': 10
})).to.be.true;
done();
});
});
it('should be false when change greater-than viewport', function (done) {
$(document).ready(() => {
const editor = init({
height: 100,
viewport: true,
license: 'lgpl-separate-notice',
lhs: (setValue) => setValue(macbeth),
rhs: (setValue) => setValue(macbeth)
});
const { mergely } = $('#mergely');
expect($('#mergely').mergely('_is_change_in_view', 'lhs', {from: 10, to: 20}, {
'lhs-line-from': 21,
'lhs-line-to': 22
})).to.be.false;
done();
});
});
it('should be true when change straddles viewport from', function (done) {
$(document).ready(() => {
const editor = init({
height: 100,
viewport: true,
license: 'lgpl-separate-notice',
lhs: (setValue) => setValue(macbeth),
rhs: (setValue) => setValue(macbeth)
});
const { mergely } = $('#mergely');
expect($('#mergely').mergely('_is_change_in_view', 'lhs', {from: 10, to: 20}, {
'lhs-line-from': 5,
'lhs-line-to': 11
})).to.be.true;
done();
});
});
it('should be true when change straddles viewport to', function (done) {
$(document).ready(() => {
const editor = init({
height: 100,
viewport: true,
license: 'lgpl-separate-notice',
lhs: (setValue) => setValue(macbeth),
rhs: (setValue) => setValue(macbeth)
});
const { mergely } = $('#mergely');
expect($('#mergely').mergely('_is_change_in_view', 'lhs', {from: 10, to: 20}, {
'lhs-line-from': 15,
'lhs-line-to': 21
})).to.be.true;
done();
});
});
it('should be true when change encompasses viewport', function (done) {
$(document).ready(() => {
const editor = init({
height: 100,
viewport: true,
license: 'lgpl-separate-notice',
lhs: (setValue) => setValue(macbeth),
rhs: (setValue) => setValue(macbeth)
});
const { mergely } = $('#mergely');
expect($('#mergely').mergely('_is_change_in_view', 'lhs', {from: 10, to: 20}, {
'lhs-line-from': 0,
'lhs-line-to': 25
})).to.be.true;
done();
});
});
it('should not be vulnerable to XSS', function (done) {
function initXSS(options) {
$('body').get(0).innerHTML = "<!DOCTYPE html><html lang=\"en\"><body><div id='mergely<script id=\"injected\">alert(123)</script>'></div></body></html>";
const divs = document.getElementsByTagName('div');
editor = $(divs[0]);
editor.mergely(options);
return editor;
};
$(document).ready(() => {
const editor = initXSS({
height: 100,
viewport: true,
license: 'lgpl-separate-notice',
lhs: (setValue) => setValue(macbeth),
rhs: (setValue) => setValue(macbeth)
});
expect($('body').find('#injected')).to.have.length(0, 'expected no div with id injected');
const divs = document.getElementsByTagName('div');
expect(divs).to.have.length(1);
expect(divs[0].id).to.equal('mergely<script id="injected">alert(123)</script>');
done();
});
});
});
});