From 2958a401adfee3f2320b682722ad1a5ecde747e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?=
Date: Sun, 6 Nov 2016 14:58:22 +0100
Subject: [PATCH] Showing Diff inline
---
openslides/core/static/css/app.css | 8 +
openslides/core/static/js/core/pdf.js | 6 +-
openslides/motions/static/js/motions/base.js | 31 +-
openslides/motions/static/js/motions/diff.js | 317 +++++++++++++++++-
.../static/js/motions/linenumbering.js | 20 +-
.../motions/motion-detail/view-diff.html | 2 +-
tests/karma/motions/diff.service.test.js | 49 +++
.../motions/linenumbering.service.test.js | 23 ++
8 files changed, 414 insertions(+), 42 deletions(-)
diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css
index a8e30f1b4..b63f89f8c 100644
--- a/openslides/core/static/css/app.css
+++ b/openslides/core/static/css/app.css
@@ -399,6 +399,14 @@ img {
.motion-text li {
margin-left: 30px;
}
+.motion-text ins {
+ color: green;
+ text-decoration: underline;
+}
+.motion-text del {
+ color: red;
+ text-decoration: line-through;
+}
.motion-text.line-numbers-outside {
padding-left: 40px;
position: relative;
diff --git a/openslides/core/static/js/core/pdf.js b/openslides/core/static/js/core/pdf.js
index ea5aad6e1..b5a8c4ec4 100644
--- a/openslides/core/static/js/core/pdf.js
+++ b/openslides/core/static/js/core/pdf.js
@@ -394,7 +394,9 @@ angular.module('OpenSlidesApp.core.pdf', [])
"h4": ["font-size:24"],
"h5": ["font-size:22"],
"h6": ["font-size:20"],
- "a": ["color:blue", "text-decoration:underline"]
+ "a": ["color:blue", "text-decoration:underline"],
+ "del": ["color:red", "text-decoration:line-through"],
+ "ins": ["color:green", "text-decoration:underline"]
},
classStyles = {
"delete": ["color:red", "text-decoration:line-through"],
@@ -541,6 +543,8 @@ angular.module('OpenSlidesApp.core.pdf', [])
case "u":
case "em":
case "i":
+ case "ins":
+ case "del":
parseChildren(alreadyConverted, element, currentParagraph, styles.concat(elementStyles[nodeName]), diff_mode);
break;
case "table":
diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js
index 66ad23ae3..451eee99a 100644
--- a/openslides/motions/static/js/motions/base.js
+++ b/openslides/motions/static/js/motions/base.js
@@ -280,7 +280,7 @@ angular.module('OpenSlidesApp.motions', [
text = '';
for (var i = 0; i < changes.length; i++) {
text += this.getTextBetweenChangeRecommendations(versionId, (i === 0 ? null : changes[i - 1]), changes[i], highlight);
- text += changes[i].format(this, versionId, highlight);
+ text += changes[i].getDiff(this, versionId);
}
text += this.getTextRemainderAfterLastChangeRecommendation(versionId, changes);
break;
@@ -584,37 +584,16 @@ angular.module('OpenSlidesApp.motions', [
saveStatus: function() {
this.DSSave();
},
- format: function(motion, version, highlight) {
+ getDiff: function(motion, version, highlight) {
var lineLength = Config.get('motions_line_length').value,
html = lineNumberingService.insertLineNumbers(motion.getVersion(version).text, lineLength);
var data = diffService.extractRangeByLineNumbers(html, this.line_from, this.line_to),
oldText = data.outerContextStart + data.innerContextStart +
- data.html + data.innerContextEnd + data.outerContextEnd,
- oldTextWithBreaks = lineNumberingService.insertLineNumbersNode(oldText, lineLength, highlight, this.line_from),
- newTextWithBreaks = lineNumberingService.insertLineNumbersNode(this.text, lineLength, null, this.line_from);
+ data.html + data.innerContextEnd + data.outerContextEnd;
- for (var i = 0; i < oldTextWithBreaks.childNodes.length; i++) {
- diffService.addCSSClass(oldTextWithBreaks.childNodes[i], 'delete');
- }
- for (i = 0; i < newTextWithBreaks.childNodes.length; i++) {
- diffService.addCSSClass(newTextWithBreaks.childNodes[i], 'insert');
- }
-
- var mergedFragment = document.createDocumentFragment(),
- el;
- while (oldTextWithBreaks.firstChild) {
- el = oldTextWithBreaks.firstChild;
- oldTextWithBreaks.removeChild(el);
- mergedFragment.appendChild(el);
- }
- while (newTextWithBreaks.firstChild) {
- el = newTextWithBreaks.firstChild;
- newTextWithBreaks.removeChild(el);
- mergedFragment.appendChild(el);
- }
-
- return diffService._serializeDom(mergedFragment);
+ var diff = diffService.diff(oldText, this.text, lineLength, this.line_from);
+ return lineNumberingService.insertLineNumbers(diff, lineLength, highlight, null, this.line_from);
},
getType: function(original_full_html) {
var lineLength = Config.get('motions_line_length').value,
diff --git a/openslides/motions/static/js/motions/diff.js b/openslides/motions/static/js/motions/diff.js
index e9b4bfc5b..21403b479 100644
--- a/openslides/motions/static/js/motions/diff.js
+++ b/openslides/motions/static/js/motions/diff.js
@@ -524,22 +524,49 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
return out;
};
+ /**
+ *
+ * @param {string} html
+ * @returns {string}
+ * @private
+ */
+ this._normalizeHtmlForDiff = function (html) {
+ // Convert all HTML tags to uppercase, strip trailing whitespaces
+ html = html.replace(/<[^>]+>/g, function (tag) {
+ return tag.toUpperCase();
+ });
+
+ var entities = {
+ ' ': ' ',
+ '–': '-',
+ 'ä': 'ä',
+ 'ö': 'ö',
+ 'ü': 'ü',
+ 'Ä': 'Ä',
+ 'Ö': 'Ö',
+ 'Ü': 'Ü',
+ 'ß': 'ß'
+ };
+
+ html = html.replace(/\s+<\/P>/gi, '
').replace(/\s+<\/DIV>/gi, '').replace(/\s+<\/LI>/gi, '');
+ html = html.replace(/\s+/gi, '').replace(/<\/LI>\s+/gi, '');
+ html = html.replace(/\u00A0/g, ' ');
+ html = html.replace(/\u2013/g, '-');
+ for (var ent in entities) {
+ html = html.replace(new RegExp(ent, 'g'), entities[ent]);
+ }
+
+ return html;
+ };
+
/**
* @param {string} htmlOld
* @param {string} htmlNew
* @returns {number}
*/
this.detectReplacementType = function (htmlOld, htmlNew) {
- // Convert all HTML tags to uppercase, strip trailing whitespaces
- var normalizeHtml = function(html) {
- html = html.replace(/<[^>]+>/g, function(tag) { return tag.toUpperCase(); });
- html = html.replace(/\s+<\/P>/gi, '').replace(/\s+<\/DIV>/gi, '').replace(/\s+<\/LI>/gi, '');
- html = html.replace(/\s+/gi, '').replace(/<\/LI>\s+/gi, '');
- html = html.replace(/ /gi, ' ').replace(/\u00A0/g, ' '); // non-breaking spaces
- return html;
- };
- htmlOld = normalizeHtml(htmlOld);
- htmlNew = normalizeHtml(htmlNew);
+ htmlOld = this._normalizeHtmlForDiff(htmlOld);
+ htmlNew = this._normalizeHtmlForDiff(htmlNew);
if (htmlOld == htmlNew) {
return this.TYPE_REPLACEMENT;
@@ -609,7 +636,7 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
if (classes.indexOf(className) == -1) {
classes.push(className);
}
- node.setAttribute('class', classes);
+ node.setAttribute('class', classes.join(' '));
};
this.addDiffMarkup = function (originalHTML, newHTML, fromLine, toLine, diffFormatterCb) {
@@ -651,6 +678,274 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
return this._serializeDom(mergedFragment, true);
};
+
+ /**
+ * Adapted from http://ejohn.org/projects/javascript-diff-algorithm/
+ * by John Resig, MIT License
+ * @param {array} oldArr
+ * @param {array} newArr
+ * @returns {object}
+ */
+ this._diff = function (oldArr, newArr) {
+ var ns = {},
+ os = {},
+ i;
+
+ for (i = 0; i < newArr.length; i++) {
+ if (ns[newArr[i]] === undefined)
+ ns[newArr[i]] = {rows: [], o: null};
+ ns[newArr[i]].rows.push(i);
+ }
+
+ for (i = 0; i < oldArr.length; i++) {
+ if (os[oldArr[i]] === undefined)
+ os[oldArr[i]] = {rows: [], n: null};
+ os[oldArr[i]].rows.push(i);
+ }
+
+ for (i in ns) {
+ if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
+ newArr[ns[i].rows[0]] = {text: newArr[ns[i].rows[0]], row: os[i].rows[0]};
+ oldArr[os[i].rows[0]] = {text: oldArr[os[i].rows[0]], row: ns[i].rows[0]};
+ }
+ }
+
+ for (i = 0; i < newArr.length - 1; i++) {
+ if (newArr[i].text !== null && newArr[i + 1].text === undefined && newArr[i].row + 1 < oldArr.length &&
+ oldArr[newArr[i].row + 1].text === undefined && newArr[i + 1] == oldArr[newArr[i].row + 1]) {
+ newArr[i + 1] = {text: newArr[i + 1], row: newArr[i].row + 1};
+ oldArr[newArr[i].row + 1] = {text: oldArr[newArr[i].row + 1], row: i + 1};
+ }
+ }
+
+ for (i = newArr.length - 1; i > 0; i--) {
+ if (newArr[i].text !== null && newArr[i - 1].text === undefined && newArr[i].row > 0 &&
+ oldArr[newArr[i].row - 1].text === undefined && newArr[i - 1] == oldArr[newArr[i].row - 1]) {
+ newArr[i - 1] = {text: newArr[i - 1], row: newArr[i].row - 1};
+ oldArr[newArr[i].row - 1] = {text: oldArr[newArr[i].row - 1], row: i - 1};
+ }
+ }
+
+ return {o: oldArr, n: newArr};
+ };
+
+ this._tokenizeHtml = function (str) {
+ var splitArrayEntries = function (arr, by, prepend) {
+ var newArr = [];
+ for (var i = 0; i < arr.length; i++) {
+ if (arr[i][0] == '<' && (by == " " || by == "\n")) {
+ // Don't split HTML tags
+ newArr.push(arr[i]);
+ continue;
+ }
+
+ var parts = arr[i].split(by);
+ if (parts.length == 1) {
+ newArr.push(arr[i]);
+ } else {
+ var j;
+ if (prepend) {
+ if (parts[0] !== '') {
+ newArr.push(parts[0]);
+ }
+ for (j = 1; j < parts.length; j++) {
+ newArr.push(by + parts[j]);
+ }
+ } else {
+ for (j = 0; j < parts.length - 1; j++) {
+ newArr.push(parts[j] + by);
+ }
+ if (parts[parts.length - 1] !== '') {
+ newArr.push(parts[parts.length - 1]);
+ }
+ }
+ }
+ }
+ return newArr;
+ };
+ var arr = splitArrayEntries([str], '<', true);
+ arr = splitArrayEntries(arr, '>', false);
+ arr = splitArrayEntries(arr, " ", false);
+ arr = splitArrayEntries(arr, "\n", false);
+ return arr;
+ };
+
+ this._outputcharcode = function (pre, str) {
+ var arr = [];
+ for (var i = 0; i < str.length; i++) {
+ arr.push(str.charCodeAt(i));
+ }
+ console.log(str, pre, arr);
+ };
+
+ /**
+ * @param {string} oldStr
+ * @param {string} newStr
+ * @returns {string}
+ */
+ this._diffString = function (oldStr, newStr) {
+ oldStr = this._normalizeHtmlForDiff(oldStr.replace(/\s+$/, '').replace(/^\s+/, ''));
+ newStr = this._normalizeHtmlForDiff(newStr.replace(/\s+$/, '').replace(/^\s+/, ''));
+
+ var out = this._diff(this._tokenizeHtml(oldStr), this._tokenizeHtml(newStr));
+ var str = "";
+ var i;
+
+ if (out.n.length === 0) {
+ for (i = 0; i < out.o.length; i++) {
+ //this._outputcharcode('del', out.o[i]);
+ str += '' + out.o[i] + "";
+ }
+ } else {
+ if (out.n[0].text === undefined) {
+ for (var k = 0; k < out.o.length && out.o[k].text === undefined; k++) {
+ //this._outputcharcode('del', out.o[k]);
+ str += '' + out.o[k] + "";
+ }
+ }
+
+ for (i = 0; i < out.n.length; i++) {
+ if (out.n[i].text === undefined) {
+ //this._outputcharcode('ins', out.n[i]);
+ str += '' + out.n[i] + "";
+ } else {
+ var pre = "";
+
+ for (var j = out.n[i].row + 1; j < out.o.length && out.o[j].text === undefined; j++) {
+ //this._outputcharcode('del', out.o[j]);
+ pre += '' + out.o[j] + "";
+ }
+ str += out.n[i].text + pre;
+ }
+ }
+ }
+
+ return str.replace(/^\s+/g, '').replace(/\s+$/g, '').replace(/ {2,}/g, ' ');
+ };
+
+ /**
+ *
+ * @param {string} html
+ * @returns {boolean}
+ * @private
+ */
+ this._diffDetectBrokenDiffHtml = function(html) {
+ var match = html.match(/<(ins|del)><[^>]*><\/(ins|del)>/gi);
+ return (match !== null && match.length > 0);
+ };
+
+ this._diffParagraphs = function(oldText, newText, lineLength, firstLineNumber) {
+ var oldTextWithBreaks, newTextWithBreaks;
+
+ if (lineLength !== undefined) {
+ oldTextWithBreaks = lineNumberingService.insertLineNumbersNode(oldText, lineLength, null, firstLineNumber);
+ newTextWithBreaks = lineNumberingService.insertLineNumbersNode(newText, lineLength, null, firstLineNumber);
+ } else {
+ oldTextWithBreaks = document.createElement('div');
+ oldTextWithBreaks.innerHTML = oldText;
+ newTextWithBreaks = document.createElement('div');
+ newTextWithBreaks.innerHTML = newText;
+ }
+
+ for (var i = 0; i < oldTextWithBreaks.childNodes.length; i++) {
+ this.addCSSClass(oldTextWithBreaks.childNodes[i], 'delete');
+ }
+ for (i = 0; i < newTextWithBreaks.childNodes.length; i++) {
+ this.addCSSClass(newTextWithBreaks.childNodes[i], 'insert');
+ }
+
+ var mergedFragment = document.createDocumentFragment(),
+ el;
+ while (oldTextWithBreaks.firstChild) {
+ el = oldTextWithBreaks.firstChild;
+ oldTextWithBreaks.removeChild(el);
+ mergedFragment.appendChild(el);
+ }
+ while (newTextWithBreaks.firstChild) {
+ el = newTextWithBreaks.firstChild;
+ newTextWithBreaks.removeChild(el);
+ mergedFragment.appendChild(el);
+ }
+
+ return this._serializeDom(mergedFragment);
+ };
+
+ /**
+ * This function calculates the diff between two strings and tries to fix problems with the resulting HTML.
+ * If lineLength and firstLineNumber is given, line numbers will be returned es well
+ *
+ * @param {number} lineLength
+ * @param {number} firstLineNumber
+ * @param {string} htmlOld
+ * @param {string} htmlNew
+ * @returns {string}
+ */
+ this.diff = function (htmlOld, htmlNew, lineLength, firstLineNumber) {
+ var cacheKey = lineLength + ' ' + firstLineNumber + ' ' +
+ lineNumberingService.djb2hash(htmlOld) + lineNumberingService.djb2hash(htmlNew),
+ cached = diffCache.get(cacheKey);
+ if (!angular.isUndefined(cached)) {
+ return cached;
+ }
+
+ var str = this._diffString(htmlOld, htmlNew),
+ diffUnnormalized = str.replace(/^\s+/g, '').replace(/\s+$/g, '').replace(/ {2,}/g, ' ')
+ .replace(/<\/ins>/gi, '').replace(/<\/del>/gi, '');
+
+ diffUnnormalized = diffUnnormalized.replace(/([a-z0-9,_-]* ?)<\/del>([a-z0-9,_-]* ?)<\/ins>/gi, function (found, oldText, newText) {
+ var foundDiff = false, commonStart = '', commonEnd = '',
+ remainderOld = oldText, remainderNew = newText;
+
+ while (remainderOld.length > 0 && remainderNew.length > 0 && !foundDiff) {
+ if (remainderOld[0] == remainderNew[0]) {
+ commonStart += remainderOld[0];
+ remainderOld = remainderOld.substr(1);
+ remainderNew = remainderNew.substr(1);
+ } else {
+ foundDiff = true;
+ }
+ }
+
+ foundDiff = false;
+ while (remainderOld.length > 0 && remainderNew.length > 0 && !foundDiff) {
+ if (remainderOld[remainderOld.length - 1] == remainderNew[remainderNew.length - 1]) {
+ commonEnd = remainderOld[remainderOld.length - 1] + commonEnd;
+ remainderNew = remainderNew.substr(0, remainderNew.length - 1);
+ remainderOld = remainderOld.substr(0, remainderOld.length - 1);
+ } else {
+ foundDiff = true;
+ }
+ }
+
+ var out = commonStart;
+ if (remainderOld !== '') {
+ out += '' + remainderOld + '';
+ }
+ if (remainderNew !== '') {
+ out += '' + remainderNew + '';
+ }
+ out += commonEnd;
+
+ return out;
+ });
+
+ var diff;
+ if (this._diffDetectBrokenDiffHtml(diffUnnormalized)) {
+ diff = this._diffParagraphs(htmlOld, htmlNew, lineLength, firstLineNumber);
+ } else {
+ var node = document.createElement('div');
+ node.innerHTML = diffUnnormalized;
+ diff = node.innerHTML;
+
+ if (lineLength !== undefined && firstLineNumber !== undefined) {
+ node = lineNumberingService.insertLineNumbersNode(diff, lineLength, null, firstLineNumber);
+ diff = node.innerHTML;
+ }
+ }
+
+ diffCache.put(cacheKey, diff);
+ return diff;
+ };
}
]);
diff --git a/openslides/motions/static/js/motions/linenumbering.js b/openslides/motions/static/js/motions/linenumbering.js
index 01b3fc359..2b7d91c03 100644
--- a/openslides/motions/static/js/motions/linenumbering.js
+++ b/openslides/motions/static/js/motions/linenumbering.js
@@ -9,6 +9,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
*
* Only the following inline elements are supported:
* - 'SPAN', 'A', 'EM', 'S', 'B', 'I', 'STRONG', 'U', 'BIG', 'SMALL', 'SUB', 'SUP', 'TT'
+ * - 'INS' and 'DEL' are supported, but line numbering does not affect the content of 'INS'-elements
*
* Only other inline elements are allowed within inline elements.
* No constructs like are allowed. CSS-attributes like 'display: block' are ignored.
@@ -37,11 +38,15 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
this._isInlineElement = function (node) {
var inlineElements = [
- 'SPAN', 'A', 'EM', 'S', 'B', 'I', 'STRONG', 'U', 'BIG', 'SMALL', 'SUB', 'SUP', 'TT'
+ 'SPAN', 'A', 'EM', 'S', 'B', 'I', 'STRONG', 'U', 'BIG', 'SMALL', 'SUB', 'SUP', 'TT', 'INS', 'DEL'
];
return (inlineElements.indexOf(node.nodeName) > -1);
};
+ this._isIgnoredByLineNumbering = function (node) {
+ return (node.nodeName == 'INS');
+ };
+
this._isOsLineBreakNode = function (node) {
var isLineBreak = false;
if (node && node.nodeType === ELEMENT_NODE && node.nodeName == 'BR' && node.hasAttribute('class')) {
@@ -304,7 +309,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
} else if (oldChildren[i].nodeType == ELEMENT_NODE) {
var firstword = this._lengthOfFirstInlineWord(oldChildren[i]),
overlength = ((this._currentInlineOffset + firstword) > length && this._currentInlineOffset > 0);
- if (overlength && this._isInlineElement(oldChildren[i])) {
+ if (overlength && this._isInlineElement(oldChildren[i]) && !this._isIgnoredByLineNumbering(oldChildren[i])) {
this._currentInlineOffset = 0;
node.appendChild(this._createLineBreak());
node.appendChild(this._createLineNumber());
@@ -327,7 +332,9 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
if (node.nodeType !== ELEMENT_NODE) {
throw 'This method may only be called for ELEMENT-nodes: ' + node.nodeValue;
}
- if (this._isInlineElement(node)) {
+ if (this._isIgnoredByLineNumbering(node)) {
+ return node;
+ } else if (this._isInlineElement(node)) {
return this._insertLineNumbersToInlineNode(node, length, highlight);
} else {
var newLength = this._calcBlockNodeLength(node, length);
@@ -357,6 +364,13 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
return root.innerHTML;
};
+ /**
+ *
+ * @param {string} html
+ * @param {number} lineLength
+ * @param {number} highlight - optional
+ * @param {number} firstLine
+ */
this.insertLineNumbersNode = function (html, lineLength, highlight, firstLine) {
var root = document.createElement('div');
root.innerHTML = html;
diff --git a/openslides/motions/static/templates/motions/motion-detail/view-diff.html b/openslides/motions/static/templates/motions/motion-detail/view-diff.html
index 3b5c4f6ca..46dc0bd52 100644
--- a/openslides/motions/static/templates/motions/motion-detail/view-diff.html
+++ b/openslides/motions/static/templates/motions/motion-detail/view-diff.html
@@ -36,7 +36,7 @@
+ ng-bind-html="change.getDiff(motion, version, highlight) | trusted">
diff --git a/tests/karma/motions/diff.service.test.js b/tests/karma/motions/diff.service.test.js
index 1d7acc4cc..d3598f45c 100644
--- a/tests/karma/motions/diff.service.test.js
+++ b/tests/karma/motions/diff.service.test.js
@@ -287,4 +287,53 @@ describe('linenumbering', function () {
expect(calculatedType).toBe(diffService.TYPE_REPLACEMENT);
});
});
+
+ describe('the core diff algorithm', function() {
+ it('acts as documented by the official documentation', function () {
+ var before = "The red brown fox jumped over the rolling log.",
+ after = "The brown spotted fox leaped over the rolling log.";
+ var diff = diffService.diff(before, after);
+ expect(diff).toBe('The red brown spotted fox jumleaped over the rolling log.');
+ });
+
+ it('ignores changing cases in HTML tags', function () {
+ var before = "The brown spotted fox jumped over the rolling log.",
+ after = "The brown spotted fox leaped over the rolling log.";
+ var diff = diffService.diff(before, after);
+
+ expect(diff).toBe('The brown spotted fox jumleaped over the rolling log.');
+ });
+
+ it('merges multiple inserts and deletes', function () {
+ var before = "Test1 Test2 Test3 Test4 Test5 Test9",
+ after = "Test1 Test6 Test7 Test8 Test9";
+ var diff = diffService.diff(before, after);
+
+ expect(diff).toBe('Test1 Test2 Test3 Test4 Test5 Test6 Test7 Test8 Test9');
+ });
+
+ it('detects insertions and deletions in a word (1)', function () {
+ var before = "Test1 Test2 Test3 Test4 Test5 Test6 Test7",
+ after = "Test1 Test Test3 Test4addon Test5 Test6 Test7";
+ var diff = diffService.diff(before, after);
+
+ expect(diff).toBe('Test1 Test2 Test3 Test4addon Test5 Test6 Test7');
+ });
+
+ it('detects insertions and deletions in a word (2)', function () {
+ var before = "Test Test",
+ after = "Test Testappend";
+ var diff = diffService.diff(before, after);
+
+ expect(diff).toBe('Test Testappend');
+ });
+
+ it('cannot handle changing CSS-classes', function () {
+ var before = "Test1 Test2
",
+ after = "Test1 Test2
";
+ var diff = diffService.diff(before, after);
+
+ expect(diff).toBe("Test1 Test2
Test1 Test2
");
+ });
+ });
});
diff --git a/tests/karma/motions/linenumbering.service.test.js b/tests/karma/motions/linenumbering.service.test.js
index 0f627ef99..bfa747577 100644
--- a/tests/karma/motions/linenumbering.service.test.js
+++ b/tests/karma/motions/linenumbering.service.test.js
@@ -126,6 +126,13 @@ describe('linenumbering', function () {
expect(outHtml).toBe(noMarkup(1) + 'Lorem ' + brMarkup(2) + 'ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit ' + brMarkup(5) + 'amet');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
});
+
+ it('counts within DEL nodes', function () {
+ var inHtml = "1234 1234 1234 1234";
+ var outHtml = lineNumberingService.insertLineNumbers(inHtml, 10);
+ expect(outHtml).toBe(noMarkup(1) + '1234 1234 ' + brMarkup(2) + '1234 1234');
+ expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
+ });
});
@@ -228,4 +235,20 @@ describe('linenumbering', function () {
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
});
});
+
+ describe('line numbering in regard to the inline diff', function() {
+ it('does not count within INS nodes', function () {
+ var inHtml = "1234 1234 1234 1234";
+ var outHtml = lineNumberingService.insertLineNumbers(inHtml, 10);
+ expect(outHtml).toBe(noMarkup(1) + '1234 1234 1234 ' + brMarkup(2) + '1234');
+ expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
+ });
+
+ it('does not create a new line for a trailing INS', function () {
+ var inHtml = "et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, conseteturdsfsdf23
";
+ var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
+ expect(outHtml).toBe('' + noMarkup(1) + 'et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata ' + brMarkup(2) + 'sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, conseteturdsfsdf23
');
+ expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
+ });
+ });
});