Separates diff processing from diff building
Moves the diff-processing functionality of the gr-diff-builder component
into a new gr-diff-processor component which exposes a promise-based
interface. This is step one of creating an asynchronous (non-blocking)
diff rendering system.
As much as possible, this change is a transfer of code (with tests) from
one component to another, making it easier to verify that functionality
has not changed. Cleanup of the code, and refactoring it into a
more-testable form will come with later changes.
Feature: Issue 3916
Change-Id: I875b03b20bf953b128cbe3c5001ba1f8eba12c61
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.html b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.html
new file mode 100644
index 0000000..cae8bad
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.html
@@ -0,0 +1,23 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<dom-module id="gr-diff-processor">
+ <script src="../gr-diff/gr-diff-line.js"></script>
+ <script src="../gr-diff/gr-diff-group.js"></script>
+ <script src="gr-diff-processor.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
new file mode 100644
index 0000000..0d787c1
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
@@ -0,0 +1,303 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ var WHOLE_FILE = -1;
+
+ var DiffSide = {
+ LEFT: 'left',
+ RIGHT: 'right',
+ };
+
+ var DiffGroupType = {
+ ADDED: 'b',
+ BOTH: 'ab',
+ REMOVED: 'a',
+ };
+
+ var DiffHighlights = {
+ ADDED: 'edit_b',
+ REMOVED: 'edit_a',
+ };
+
+ Polymer({
+ is: 'gr-diff-processor',
+
+ properties: {
+
+ /**
+ * The amount of context around collapsed groups.
+ */
+ context: Number,
+
+ /**
+ * The array of groups output by the processor.
+ */
+ groups: {
+ type: Array,
+ notify: true,
+ },
+
+ /**
+ * Locations that should not be collapsed, including the locations of
+ * comments.
+ */
+ keyLocations: {
+ type: Object,
+ value: function() { return {left: {}, right: {}}; },
+ },
+
+ _content: Object,
+ },
+
+ process: function(content) {
+ return new Promise(function(resolve) {
+ var groups = [];
+ this._processContent(content, groups, this.context);
+ this.groups = groups;
+ resolve(groups);
+ }.bind(this));
+ },
+
+ _processContent: function(content, groups, context) {
+ this._appendFileComments(groups);
+
+ context = content.length > 1 ? context : WHOLE_FILE;
+
+ var lineNums = {
+ left: 0,
+ right: 0,
+ };
+ content = this._splitCommonGroupsWithComments(content, lineNums);
+ for (var i = 0; i < content.length; i++) {
+ var group = content[i];
+ var lines = [];
+
+ if (group[DiffGroupType.BOTH] !== undefined) {
+ var rows = group[DiffGroupType.BOTH];
+ this._appendCommonLines(rows, lines, lineNums);
+
+ var hiddenRange = [context, rows.length - context];
+ if (i === 0) {
+ hiddenRange[0] = 0;
+ } else if (i === content.length - 1) {
+ hiddenRange[1] = rows.length;
+ }
+
+ if (context !== WHOLE_FILE && hiddenRange[1] - hiddenRange[0] > 0) {
+ this._insertContextGroups(groups, lines, hiddenRange);
+ } else {
+ groups.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, lines));
+ }
+ continue;
+ }
+
+ if (group[DiffGroupType.REMOVED] !== undefined) {
+ var highlights = undefined;
+ if (group[DiffHighlights.REMOVED] !== undefined) {
+ highlights = this._normalizeIntralineHighlights(
+ group[DiffGroupType.REMOVED],
+ group[DiffHighlights.REMOVED]);
+ }
+ this._appendRemovedLines(group[DiffGroupType.REMOVED], lines,
+ lineNums, highlights);
+ }
+
+ if (group[DiffGroupType.ADDED] !== undefined) {
+ var highlights = undefined;
+ if (group[DiffHighlights.ADDED] !== undefined) {
+ highlights = this._normalizeIntralineHighlights(
+ group[DiffGroupType.ADDED],
+ group[DiffHighlights.ADDED]);
+ }
+ this._appendAddedLines(group[DiffGroupType.ADDED], lines,
+ lineNums, highlights);
+ }
+ groups.push(new GrDiffGroup(GrDiffGroup.Type.DELTA, lines));
+ }
+ },
+
+ _appendFileComments: function(groups) {
+ var line = new GrDiffLine(GrDiffLine.Type.BOTH);
+ line.beforeNumber = GrDiffLine.FILE;
+ line.afterNumber = GrDiffLine.FILE;
+ groups.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, [line]));
+ },
+
+ /**
+ * In order to show comments out of the bounds of the selected context,
+ * treat them as separate chunks within the model so that the content (and
+ * context surrounding it) renders correctly.
+ */
+ _splitCommonGroupsWithComments: function(content, lineNums) {
+ var result = [];
+ var leftLineNum = lineNums.left;
+ var rightLineNum = lineNums.right;
+ for (var i = 0; i < content.length; i++) {
+ if (!content[i].ab) {
+ result.push(content[i]);
+ if (content[i].a) {
+ leftLineNum += content[i].a.length;
+ }
+ if (content[i].b) {
+ rightLineNum += content[i].b.length;
+ }
+ continue;
+ }
+ var chunk = content[i].ab;
+ var currentChunk = {ab: []};
+ for (var j = 0; j < chunk.length; j++) {
+ leftLineNum++;
+ rightLineNum++;
+
+ if (this.keyLocations[DiffSide.LEFT][leftLineNum] ||
+ this.keyLocations[DiffSide.RIGHT][rightLineNum]) {
+ if (currentChunk.ab && currentChunk.ab.length > 0) {
+ result.push(currentChunk);
+ currentChunk = {ab: []};
+ }
+ result.push({ab: [chunk[j]]});
+ } else {
+ currentChunk.ab.push(chunk[j]);
+ }
+ }
+ // != instead of !== because we want to cover both undefined and null.
+ if (currentChunk.ab != null && currentChunk.ab.length > 0) {
+ result.push(currentChunk);
+ }
+ }
+ return result;
+ },
+
+ _appendCommonLines: function(rows, lines, lineNums) {
+ for (var i = 0; i < rows.length; i++) {
+ var line = new GrDiffLine(GrDiffLine.Type.BOTH);
+ line.text = rows[i];
+ line.beforeNumber = ++lineNums.left;
+ line.afterNumber = ++lineNums.right;
+ lines.push(line);
+ }
+ },
+
+ _insertContextGroups: function(groups, lines, hiddenRange) {
+ var linesBeforeCtx = lines.slice(0, hiddenRange[0]);
+ var hiddenLines = lines.slice(hiddenRange[0], hiddenRange[1]);
+ var linesAfterCtx = lines.slice(hiddenRange[1]);
+
+ if (linesBeforeCtx.length > 0) {
+ groups.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesBeforeCtx));
+ }
+
+ var ctxLine = new GrDiffLine(GrDiffLine.Type.CONTEXT_CONTROL);
+ ctxLine.contextGroup =
+ new GrDiffGroup(GrDiffGroup.Type.BOTH, hiddenLines);
+ groups.push(new GrDiffGroup(GrDiffGroup.Type.CONTEXT_CONTROL,
+ [ctxLine]));
+
+ if (linesAfterCtx.length > 0) {
+ groups.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesAfterCtx));
+ }
+ },
+
+ /**
+ * The `highlights` array consists of a list of <skip length, mark length>
+ * pairs, where the skip length is the number of characters between the
+ * end of the previous edit and the start of this edit, and the mark
+ * length is the number of edited characters following the skip. The start
+ * of the edits is from the beginning of the related diff content lines.
+ *
+ * Note that the implied newline character at the end of each line is
+ * included in the length calculation, and thus it is possible for the
+ * edits to span newlines.
+ *
+ * A line highlight object consists of three fields:
+ * - contentIndex: The index of the diffChunk `content` field (the line
+ * being referred to).
+ * - startIndex: Where the highlight should begin.
+ * - endIndex: (optional) Where the highlight should end. If omitted, the
+ * highlight is meant to be a continuation onto the next line.
+ */
+ _normalizeIntralineHighlights: function(content, highlights) {
+ var contentIndex = 0;
+ var idx = 0;
+ var normalized = [];
+ for (var i = 0; i < highlights.length; i++) {
+ var line = content[contentIndex] + '\n';
+ var hl = highlights[i];
+ var j = 0;
+ while (j < hl[0]) {
+ if (idx === line.length) {
+ idx = 0;
+ line = content[++contentIndex] + '\n';
+ continue;
+ }
+ idx++;
+ j++;
+ }
+ var lineHighlight = {
+ contentIndex: contentIndex,
+ startIndex: idx,
+ };
+
+ j = 0;
+ while (line && j < hl[1]) {
+ if (idx === line.length) {
+ idx = 0;
+ line = content[++contentIndex] + '\n';
+ normalized.push(lineHighlight);
+ lineHighlight = {
+ contentIndex: contentIndex,
+ startIndex: idx,
+ };
+ continue;
+ }
+ idx++;
+ j++;
+ }
+ lineHighlight.endIndex = idx;
+ normalized.push(lineHighlight);
+ }
+ return normalized;
+ },
+
+ _appendRemovedLines: function(rows, lines, lineNums, opt_highlights) {
+ for (var i = 0; i < rows.length; i++) {
+ var line = new GrDiffLine(GrDiffLine.Type.REMOVE);
+ line.text = rows[i];
+ line.beforeNumber = ++lineNums.left;
+ if (opt_highlights) {
+ line.highlights = opt_highlights.filter(function(hl) {
+ return hl.contentIndex === i;
+ });
+ }
+ lines.push(line);
+ }
+ },
+
+ _appendAddedLines: function(rows, lines, lineNums, opt_highlights) {
+ for (var i = 0; i < rows.length; i++) {
+ var line = new GrDiffLine(GrDiffLine.Type.ADD);
+ line.text = rows[i];
+ line.afterNumber = ++lineNums.right;
+ if (opt_highlights) {
+ line.highlights = opt_highlights.filter(function(hl) {
+ return hl.contentIndex === i;
+ });
+ }
+ lines.push(line);
+ }
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
new file mode 100644
index 0000000..6a24d6a
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
@@ -0,0 +1,406 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-diff-processor test</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-diff-processor.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-diff-processor></gr-diff-processor>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-diff-processor tests', function() {
+ var element;
+
+ suite('not logged in', function() {
+
+ setup(function() {
+ element = fixture('basic');
+
+ element.context = 4;
+ });
+
+ test('process loaded content', function(done) {
+ var content = [
+ {
+ ab: [
+ '<!DOCTYPE html>',
+ '<meta charset="utf-8">',
+ ]
+ },
+ {
+ a: [
+ ' Welcome ',
+ ' to the wooorld of tomorrow!',
+ ],
+ b: [
+ ' Hello, world!',
+ ],
+ },
+ {
+ ab: [
+ 'Leela: This is the only place the ship can’t hear us, so ',
+ 'everyone pretend to shower.',
+ 'Fry: Same as every day. Got it.',
+ ]
+ },
+ ];
+
+ element.process(content).then(function() {
+ var groups = element.groups;
+
+ assert.equal(groups.length, 4);
+
+ var group = groups[0];
+ assert.equal(group.type, GrDiffGroup.Type.BOTH);
+ assert.equal(group.lines.length, 1);
+ assert.equal(group.lines[0].text, '');
+ assert.equal(group.lines[0].beforeNumber, GrDiffLine.FILE);
+ assert.equal(group.lines[0].afterNumber, GrDiffLine.FILE);
+
+ group = groups[1];
+ assert.equal(group.type, GrDiffGroup.Type.BOTH);
+ assert.equal(group.lines.length, 2);
+ assert.equal(group.lines.length, 2);
+
+ function beforeNumberFn(l) { return l.beforeNumber; }
+ function afterNumberFn(l) { return l.afterNumber; }
+ function textFn(l) { return l.text; }
+
+ assert.deepEqual(group.lines.map(beforeNumberFn), [1, 2]);
+ assert.deepEqual(group.lines.map(afterNumberFn), [1, 2]);
+ assert.deepEqual(group.lines.map(textFn), [
+ '<!DOCTYPE html>',
+ '<meta charset="utf-8">',
+ ]);
+
+ group = groups[2];
+ assert.equal(group.type, GrDiffGroup.Type.DELTA);
+ assert.equal(group.lines.length, 3);
+ assert.equal(group.adds.length, 1);
+ assert.equal(group.removes.length, 2);
+ assert.deepEqual(group.removes.map(beforeNumberFn), [3, 4]);
+ assert.deepEqual(group.adds.map(afterNumberFn), [3]);
+ assert.deepEqual(group.removes.map(textFn), [
+ ' Welcome ',
+ ' to the wooorld of tomorrow!',
+ ]);
+ assert.deepEqual(group.adds.map(textFn), [
+ ' Hello, world!',
+ ]);
+
+ group = groups[3];
+ assert.equal(group.type, GrDiffGroup.Type.BOTH);
+ assert.equal(group.lines.length, 3);
+ assert.deepEqual(group.lines.map(beforeNumberFn), [5, 6, 7]);
+ assert.deepEqual(group.lines.map(afterNumberFn), [4, 5, 6]);
+ assert.deepEqual(group.lines.map(textFn), [
+ 'Leela: This is the only place the ship can’t hear us, so ',
+ 'everyone pretend to shower.',
+ 'Fry: Same as every day. Got it.',
+ ]);
+
+ done();
+ });
+ });
+
+ test('insert context groups', function(done) {
+ var content = [
+ {ab: []},
+ {a: ['all work and no play make andybons a dull boy']},
+ {ab: []},
+ {b: ['elgoog elgoog elgoog']},
+ {ab: []},
+ ];
+ for (var i = 0; i < 100; i++) {
+ content[0].ab.push('all work and no play make jack a dull boy');
+ content[4].ab.push('all work and no play make jill a dull girl');
+ }
+ for (var i = 0; i < 5; i++) {
+ content[2].ab.push('no tv and no beer make homer go crazy');
+ }
+
+ var context = 10;
+ element.context = context;
+
+ element.process(content).then(function() {
+ var groups = element.groups;
+
+ assert.equal(groups[0].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[0].lines.length, 1);
+ assert.equal(groups[0].lines[0].text, '');
+ assert.equal(groups[0].lines[0].beforeNumber, GrDiffLine.FILE);
+ assert.equal(groups[0].lines[0].afterNumber, GrDiffLine.FILE);
+
+ assert.equal(groups[1].type, GrDiffGroup.Type.CONTEXT_CONTROL);
+ assert.instanceOf(groups[1].lines[0].contextGroup, GrDiffGroup);
+ assert.equal(groups[1].lines[0].contextGroup.lines.length, 90);
+ groups[1].lines[0].contextGroup.lines.forEach(function(l) {
+ assert.equal(l.text, content[0].ab[0]);
+ });
+
+ assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[2].lines.length, context);
+ groups[2].lines.forEach(function(l) {
+ assert.equal(l.text, content[0].ab[0]);
+ });
+
+ assert.equal(groups[3].type, GrDiffGroup.Type.DELTA);
+ assert.equal(groups[3].lines.length, 1);
+ assert.equal(groups[3].removes.length, 1);
+ assert.equal(groups[3].removes[0].text,
+ 'all work and no play make andybons a dull boy');
+
+ assert.equal(groups[4].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[4].lines.length, 5);
+ groups[4].lines.forEach(function(l) {
+ assert.equal(l.text, content[2].ab[0]);
+ });
+
+ assert.equal(groups[5].type, GrDiffGroup.Type.DELTA);
+ assert.equal(groups[5].lines.length, 1);
+ assert.equal(groups[5].adds.length, 1);
+ assert.equal(groups[5].adds[0].text, 'elgoog elgoog elgoog');
+
+ assert.equal(groups[6].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[6].lines.length, context);
+ groups[6].lines.forEach(function(l) {
+ assert.equal(l.text, content[4].ab[0]);
+ });
+
+ assert.equal(groups[7].type, GrDiffGroup.Type.CONTEXT_CONTROL);
+ assert.instanceOf(groups[7].lines[0].contextGroup, GrDiffGroup);
+ assert.equal(groups[7].lines[0].contextGroup.lines.length, 90);
+ groups[7].lines[0].contextGroup.lines.forEach(function(l) {
+ assert.equal(l.text, content[4].ab[0]);
+ });
+
+ done();
+ });
+ });
+
+ test('insert context groups', function(done) {
+ content = [
+ {a: ['all work and no play make andybons a dull boy']},
+ {ab: []},
+ {b: ['elgoog elgoog elgoog']},
+ ];
+ for (var i = 0; i < 50; i++) {
+ content[1].ab.push('no tv and no beer make homer go crazy');
+ }
+
+ var context = 10;
+ element.context = context;
+
+ element.process(content).then(function() {
+ var groups = element.groups;
+
+ assert.equal(groups[0].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[0].lines.length, 1);
+ assert.equal(groups[0].lines[0].text, '');
+ assert.equal(groups[0].lines[0].beforeNumber, GrDiffLine.FILE);
+ assert.equal(groups[0].lines[0].afterNumber, GrDiffLine.FILE);
+
+ assert.equal(groups[1].type, GrDiffGroup.Type.DELTA);
+ assert.equal(groups[1].lines.length, 1);
+ assert.equal(groups[1].removes.length, 1);
+ assert.equal(groups[1].removes[0].text,
+ 'all work and no play make andybons a dull boy');
+
+ assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[2].lines.length, context);
+ groups[2].lines.forEach(function(l) {
+ assert.equal(l.text, content[1].ab[0]);
+ });
+
+ assert.equal(groups[3].type, GrDiffGroup.Type.CONTEXT_CONTROL);
+ assert.instanceOf(groups[3].lines[0].contextGroup, GrDiffGroup);
+ assert.equal(groups[3].lines[0].contextGroup.lines.length, 30);
+ groups[3].lines[0].contextGroup.lines.forEach(function(l) {
+ assert.equal(l.text, content[1].ab[0]);
+ });
+
+ assert.equal(groups[4].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[4].lines.length, context);
+ groups[4].lines.forEach(function(l) {
+ assert.equal(l.text, content[1].ab[0]);
+ });
+
+ assert.equal(groups[5].type, GrDiffGroup.Type.DELTA);
+ assert.equal(groups[5].lines.length, 1);
+ assert.equal(groups[5].adds.length, 1);
+ assert.equal(groups[5].adds[0].text, 'elgoog elgoog elgoog');
+
+ done();
+ });
+ });
+
+ test('break up common diff chunks', function() {
+ element.keyLocations = {
+ left: {1: true},
+ right: {10: true},
+ };
+ var lineNums = {left: 0, right: 0};
+
+ var content = [
+ {
+ ab: [
+ 'Copyright (C) 2015 The Android Open Source Project',
+ '',
+ 'Licensed under the Apache License, Version 2.0 (the "License");',
+ 'you may not use this file except in compliance with the ' +
+ 'License.',
+ 'You may obtain a copy of the License at',
+ '',
+ 'http://www.apache.org/licenses/LICENSE-2.0',
+ '',
+ 'Unless required by applicable law or agreed to in writing, ',
+ 'software distributed under the License is distributed on an ',
+ '"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, ',
+ 'either express or implied. See the License for the specific ',
+ 'language governing permissions and limitations under the ' +
+ 'License.',
+ ]
+ }
+ ];
+ var result = element._splitCommonGroupsWithComments(content, lineNums);
+ assert.deepEqual(result, [
+ {
+ ab: ['Copyright (C) 2015 The Android Open Source Project'],
+ },
+ {
+ ab: [
+ '',
+ 'Licensed under the Apache License, Version 2.0 (the "License");',
+ 'you may not use this file except in compliance with the ' +
+ 'License.',
+ 'You may obtain a copy of the License at',
+ '',
+ 'http://www.apache.org/licenses/LICENSE-2.0',
+ '',
+ 'Unless required by applicable law or agreed to in writing, ',
+ ]
+ },
+ {
+ ab: [
+ 'software distributed under the License is distributed on an '],
+ },
+ {
+ ab: [
+ '"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, ',
+ 'either express or implied. See the License for the specific ',
+ 'language governing permissions and limitations under the ' +
+ 'License.',
+ ]
+ }
+ ]);
+ });
+
+ test('intraline normalization', function() {
+ // The content and highlights are in the format returned by the Gerrit
+ // REST API.
+ var content = [
+ ' <section class="summary">',
+ ' <gr-linked-text content="' +
+ '[[_computeCurrentRevisionMessage(change)]]"></gr-linked-text>',
+ ' </section>',
+ ];
+ var highlights = [
+ [31, 34], [42, 26]
+ ];
+
+ var results = element._normalizeIntralineHighlights(content,
+ highlights);
+ assert.deepEqual(results, [
+ {
+ contentIndex: 0,
+ startIndex: 31,
+ },
+ {
+ contentIndex: 1,
+ startIndex: 0,
+ endIndex: 33,
+ },
+ {
+ contentIndex: 1,
+ startIndex: 75,
+ },
+ {
+ contentIndex: 2,
+ startIndex: 0,
+ endIndex: 6,
+ }
+ ]);
+
+ content = [
+ ' this._path = value.path;',
+ '',
+ ' // When navigating away from the page, there is a ' +
+ 'possibility that the',
+ ' // patch number is no longer a part of the URL ' +
+ '(say when navigating to',
+ ' // the top-level change info view) and therefore ' +
+ 'undefined in `params`.',
+ ' if (!this._patchRange.patchNum) {',
+ ];
+ highlights = [
+ [14, 17],
+ [11, 70],
+ [12, 67],
+ [12, 67],
+ [14, 29],
+ ];
+ results = element._normalizeIntralineHighlights(content, highlights);
+ assert.deepEqual(results, [
+ {
+ contentIndex: 0,
+ startIndex: 14,
+ endIndex: 31,
+ },
+ {
+ contentIndex: 2,
+ startIndex: 8,
+ endIndex: 78,
+ },
+ {
+ contentIndex: 3,
+ startIndex: 11,
+ endIndex: 78,
+ },
+ {
+ contentIndex: 4,
+ startIndex: 11,
+ endIndex: 78,
+ },
+ {
+ contentIndex: 5,
+ startIndex: 12,
+ endIndex: 41,
+ }
+ ]);
+ });
+ });
+ });
+</script>