| <!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-highlight</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-highlight.html"> |
| |
| <test-fixture id="basic"> |
| <template> |
| <gr-diff-highlight> |
| <table id="diffTable"> |
| |
| <tbody class="section both"> |
| <tr class="diff-row side-by-side" left-type="both" right-type="both"> |
| <td class="left lineNum" data-value="138">138</td> |
| <td class="content both darkHighlight">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</td> |
| <td class="right lineNum" data-value="119">119</td> |
| <td class="content both darkHighlight">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</td> |
| </tr> |
| </tbody> |
| |
| <tbody class="section delta"> |
| <tr class="diff-row side-by-side" left-type="remove" right-type="add"> |
| <td class="left lineNum" data-value="140">140</td> |
| <!-- Next tag is formatted to eliminate zero-length text nodes. --> |
| <td class="content remove lightHighlight">naπ’ti <hl class="foo">te, inquit</hl>, sumus <hl class="bar">aliquando</hl> otiosum, <hl>certe</hl> a <hl><span class="tab withIndicator" style="tab-size:8;"></span></hl>udiam, <hl>quid</hl> sit, <span class="tab withIndicator" style="tab-size:8;"></span>quod <hl>Epicurum</hl><gr-diff-comment-thread> |
| [Yet another random diff thread content here] |
| </gr-diff-comment-thread></td> |
| <td class="right lineNum" data-value="120">120</td> |
| <!-- Next tag is formatted to eliminate zero-length text nodes. --> |
| <td class="content add lightHighlight">nacti , <hl>,</hl> sumus <hl><span class="tab withIndicator" style="tab-size:8;"></span></hl> otiosum, <span class="tab withIndicator" style="tab-size:8;"></span> audiam, sit, quod</td> |
| </tr> |
| </tbody> |
| |
| <tbody class="section both"> |
| <tr class="diff-row side-by-side" left-type="both" right-type="both"> |
| <td class="left lineNum" data-value="141"></td> |
| <td class="content both darkHighlight">nam et<hl><span class="tab withIndicator" style="tab-size:8;"> </span></hl>complectitur<span class="tab withIndicator" style="tab-size:8;"> </span>verbis, quod vult, et dicit plane, quod intellegam;</td> |
| <td class="right lineNum" data-value="130"></td> |
| <td class="content both darkHighlight">nam et complectitur verbis, quod vult, et dicit plane, quod intellegam;</td> |
| </tr> |
| </tbody> |
| |
| <tbody class="section contextControl"> |
| <tr class="diff-row side-by-side" left-type="contextControl" right-type="contextControl"> |
| <td class="left contextLineNum" data-value="@@"></td> |
| <td> |
| <gr-button>+10β</gr-button> |
| - |
| <gr-button>Show 21 common lines</gr-button> |
| - |
| <gr-button>+10β</gr-button> |
| </td> |
| <td class="right contextLineNum" data-value="@@"></td> |
| <td> |
| <gr-button>+10β</gr-button> |
| - |
| <gr-button>Show 21 common lines</gr-button> |
| - |
| <gr-button>+10β</gr-button> |
| </td> |
| </tr> |
| </tbody> |
| |
| <tbody class="section delta"> |
| <tr class="diff-row side-by-side" left-type="blank" right-type="add"> |
| <td class="left"></td> |
| <td class="blank darkHighlight"></td> |
| <td class="right lineNum" data-value="146"></td> |
| <td class="content add darkHighlight">[17] Quid igitur est? inquit; audire enim cupio, quid non probes. Principio, inquam,</td> |
| </tr> |
| </tbody> |
| |
| <tbody class="section both"> |
| <tr class="diff-row side-by-side" left-type="both" right-type="both"> |
| <td class="left lineNum" data-value="165"></td> |
| <td class="content both darkHighlight">in physicis, quibus maxime gloriatur, primum totus est alienus. Democritea dicit</td> |
| <td class="right lineNum" data-value="147"></td> |
| <td class="content both darkHighlight">in physicis, <hl><span class="tab withIndicator" style="tab-size:8;"> </span></hl> quibus maxime gloriatur, primum totus est alienus. Democritea dicit</td> |
| </tr> |
| </tbody> |
| |
| </table> |
| </gr-diff-highlight> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="highlighted"> |
| <template> |
| <div> |
| <hl class="rangeHighlight">foo</hl> |
| bar |
| <hl class="rangeHighlight">baz</hl> |
| </div> |
| </template> |
| </test-fixture> |
| |
| <script> |
| suite('gr-diff-highlight', function() { |
| var element; |
| var sandbox; |
| |
| setup(function() { |
| sandbox = sinon.sandbox.create(); |
| element = fixture('basic'); |
| }); |
| |
| teardown(function() { |
| sandbox.restore(); |
| }); |
| |
| test('_enabledListeners', function() { |
| var listeners = element._enabledListeners; |
| for (var eventName in listeners) { |
| sandbox.stub(element, listeners[eventName]); |
| } |
| // Enable all the listeners. |
| element.enabled = true; |
| for (var eventName in listeners) { |
| var methodName = listeners[eventName]; |
| var stub = element[methodName]; |
| element.fire(eventName); |
| assert.isTrue(stub.called); |
| stub.reset(); |
| } |
| // Disable all the listeners. |
| element.enabled = false; |
| for (var eventName in listeners) { |
| var methodName = listeners[eventName]; |
| var stub = element[methodName]; |
| element.fire(eventName); |
| assert.isFalse(stub.called); |
| } |
| }); |
| |
| test('does not listen to selectionchange when disabled', function() { |
| sandbox.stub(element, '_handleSelection'); |
| sandbox.stub(element, '_removeActionBox'); |
| element.enabled = false; |
| document.dispatchEvent(new CustomEvent('selectionchange')); |
| element.flushDebouncer('selectionChange'); |
| assert.isFalse(element._handleSelection.called); |
| element.flushDebouncer('removeActionBox'); |
| assert.isFalse(element._removeActionBox.called); |
| }); |
| |
| test('listens to selectionchange when enabled', function() { |
| sandbox.stub(element, '_handleSelection'); |
| sandbox.stub(element, '_removeActionBox'); |
| element.enabled = true; |
| document.dispatchEvent(new CustomEvent('selectionchange')); |
| element.flushDebouncer('selectionChange'); |
| assert.isTrue(element._handleSelection.called); |
| element.flushDebouncer('removeActionBox'); |
| assert.isTrue(element._removeActionBox.called); |
| }); |
| |
| suite('comment events', function() { |
| var builder; |
| |
| setup(function() { |
| builder = { |
| getContentsByLineRange: sandbox.stub().returns([]), |
| getLineElByChild: sandbox.stub().returns({}), |
| getSideByLineEl: sandbox.stub().returns('other-side'), |
| renderLineRange: sandbox.stub(), |
| }; |
| element._cachedDiffBuilder = builder; |
| element.enabled = true; |
| }); |
| |
| test('ignores thread discard for line comment', function(done) { |
| element.fire('thread-discard', {lastComment: {}}); |
| flush(function() { |
| assert.isFalse(builder.renderLineRange.called); |
| done(); |
| }); |
| }); |
| |
| test('ignores comment discard for line comment', function(done) { |
| element.fire('comment-discard', {comment: {}}); |
| flush(function() { |
| assert.isFalse(builder.renderLineRange.called); |
| done(); |
| }); |
| }); |
| |
| test('renders lines in comment range on thread discard', function(done) { |
| element.fire('thread-discard', { |
| lastComment: { |
| range: { |
| start_line: 10, |
| end_line: 24, |
| }, |
| }, |
| }); |
| flush(function() { |
| assert.isTrue( |
| builder.renderLineRange.calledWithExactly(10, 24, 'other-side')); |
| done(); |
| }); |
| }); |
| |
| test('renders lines in comment range on comment discard', function(done) { |
| element.fire('comment-discard', { |
| comment: { |
| range: { |
| start_line: 10, |
| end_line: 24, |
| }, |
| }, |
| }); |
| flush(function() { |
| assert.isTrue( |
| builder.renderLineRange.calledWithExactly(10, 24, 'other-side')); |
| done(); |
| }); |
| }); |
| |
| test('comment-mouse-over from line comments is ignored', function() { |
| sandbox.stub(element, '_applyRangedHighlight'); |
| element.fire('comment-mouse-over', {comment: {}}); |
| assert.isFalse(element._applyRangedHighlight.called); |
| }); |
| |
| test('comment-mouse-out from line comments is ignored', function() { |
| element.fire('comment-mouse-over', {comment: {}}); |
| assert.isFalse(builder.getContentsByLineRange.called); |
| }); |
| |
| test('on comment-mouse-out highlight classes are removed', function() { |
| var testEl = fixture('highlighted'); |
| builder.getContentsByLineRange.returns([testEl]); |
| element.fire('comment-mouse-out', { |
| comment: { |
| range: { |
| start_line: 3, |
| start_character: 14, |
| end_line: 10, |
| end_character: 24, |
| } |
| }}); |
| assert.isTrue(builder.getContentsByLineRange.calledWithExactly( |
| 3, 10, 'other-side')); |
| assert.equal(0, testEl.querySelectorAll('.rangeHighlight').length); |
| assert.equal(2, testEl.querySelectorAll('.range').length); |
| }); |
| |
| test('on comment-mouse-over range is highlighted', function() { |
| sandbox.stub(element, '_applyRangedHighlight'); |
| element.fire('comment-mouse-over', { |
| comment: { |
| range: { |
| start_line: 3, |
| start_character: 14, |
| end_line: 10, |
| end_character: 24, |
| }, |
| }}); |
| assert.isTrue(element._applyRangedHighlight.calledWithExactly( |
| 'rangeHighlight', 3, 14, 10, 24, 'other-side')); |
| }); |
| |
| test('on create-comment range is highlighted', function() { |
| sandbox.stub(element, '_applyRangedHighlight'); |
| element.fire('create-comment', { |
| range: { |
| startLine: 3, |
| startChar: 14, |
| endLine: 10, |
| endChar: 24, |
| }, |
| side: 'some-side', |
| }); |
| assert.isTrue(element._applyRangedHighlight.calledWithExactly( |
| 'range', 3, 14, 10, 24, 'some-side')); |
| }); |
| |
| test('on create-comment action box is removed', function() { |
| sandbox.stub(element, '_applyRangedHighlight'); |
| sandbox.stub(element, '_removeActionBox'); |
| element.fire('create-comment', { |
| comment: { |
| range: {}, |
| }, |
| }); |
| assert.isTrue(element._removeActionBox.called); |
| }); |
| }); |
| |
| test('apply multiline highlight', function() { |
| var diff = element.querySelector('#diffTable'); |
| var startContent = |
| diff.querySelector('.left.lineNum[data-value="138"] ~ .content'); |
| var betweenContent = |
| diff.querySelector('.left.lineNum[data-value="140"] ~ .content'); |
| var endContent = |
| diff.querySelector('.left.lineNum[data-value="141"] ~ .content'); |
| var commentThread = |
| diff.querySelector('gr-diff-comment-thread'); |
| var builder = { |
| getCommentThreadByContentEl: sandbox.stub().returns(commentThread), |
| getContentByLine: sandbox.stub().returns({}), |
| getContentsByLineRange: sandbox.stub().returns([betweenContent]), |
| getLineElByChild: sandbox.stub().returns( |
| {getAttribute: sandbox.stub()}), |
| }; |
| element._cachedDiffBuilder = builder; |
| element.enabled = true; |
| builder.getContentByLine.withArgs(138, 'left').returns( |
| startContent); |
| builder.getContentByLine.withArgs(141, 'left').returns( |
| endContent); |
| element._applyRangedHighlight('some', 138, 4, 141, 28, 'left'); |
| assert.instanceOf(startContent.childNodes[0], Text); |
| assert.equal(startContent.childNodes[0].textContent, '[14]'); |
| assert.instanceOf(startContent.childNodes[1], Element); |
| assert.equal(startContent.childNodes[1].textContent, |
| ' Nam cum ad me in Cumanum salutandi causa uterque venisset,'); |
| assert.equal(startContent.childNodes[1].tagName, 'HL'); |
| assert.equal(startContent.childNodes[1].className, 'some'); |
| |
| assert.instanceOf(betweenContent.firstChild, Element); |
| assert.equal(betweenContent.firstChild.tagName, 'HL'); |
| assert.equal(betweenContent.firstChild.className, 'some'); |
| assert.equal(betweenContent.childNodes.length, 2); |
| assert.equal(betweenContent.firstChild.childNodes.length, 1); |
| assert.equal(betweenContent.firstChild.textContent, |
| 'naπ’ti te, inquit, sumus aliquando otiosum, certe a udiam, ' + |
| 'quid sit, quod Epicurum'); |
| |
| assert.isNull(diff.querySelector('.right + .content .some'), |
| 'Highlight should be applied only to the left side content.'); |
| |
| assert.strictEqual(betweenContent.querySelector('gr-diff-comment-thread'), |
| commentThread, 'Comment threads should be preserved.'); |
| |
| assert.instanceOf(endContent.childNodes[0], Element); |
| assert.equal(endContent.childNodes[0].textContent, |
| 'nam et\tcomplectitur\tverbis, '); |
| assert.equal(endContent.childNodes[0].tagName, 'HL'); |
| assert.equal(endContent.childNodes[0].className, 'some'); |
| assert.instanceOf(endContent.childNodes[1], Text); |
| assert.equal(endContent.childNodes[1].textContent, |
| 'quod vult, et dicit plane, quod intellegam;'); |
| var endHl = endContent.querySelector('hl.some'); |
| assert.equal(endHl.childNodes.length, 5); |
| var tabs = endHl.querySelectorAll('span.tab'); |
| assert.equal(tabs.length, 2); |
| assert.equal(tabs[0].previousSibling.textContent, 'nam et'); |
| assert.equal(tabs[1].previousSibling.textContent, 'complectitur'); |
| assert.equal(tabs[1].nextSibling.textContent, 'verbis, '); |
| }); |
| |
| suite('single line ranges', function() { |
| var diff; |
| var content; |
| var commentThread; |
| var builder; |
| |
| setup(function() { |
| diff = element.querySelector('#diffTable'); |
| content = |
| diff.querySelector('.left.lineNum[data-value="140"] ~ .content'); |
| commentThread = diff.querySelector('gr-diff-comment-thread'); |
| builder = { |
| getCommentThreadByContentEl: sandbox.stub().returns(commentThread), |
| getContentByLine: sandbox.stub().returns(content), |
| getContentsByLineRange: sandbox.stub().returns([]), |
| getLineElByChild: sandbox.stub().returns( |
| {getAttribute: sandbox.stub()}), |
| }; |
| element._cachedDiffBuilder = builder; |
| element.enabled = true; |
| }); |
| |
| test('whole line range', function() { |
| element._applyRangedHighlight('some', 140, 0, 140, 81, 'left'); |
| assert.instanceOf(content.firstChild, Element); |
| assert.equal(content.firstChild.tagName, 'HL'); |
| assert.equal(content.firstChild.className, 'some'); |
| assert.equal(content.childNodes.length, 2); |
| assert.equal(content.firstChild.childNodes.length, 5); |
| assert.equal(content.firstChild.textContent, |
| 'naπ’ti te, inquit, sumus aliquando otiosum, certe a udiam, ' + |
| 'quid sit, quod Epicurum'); |
| var tabs = content.querySelectorAll('span.tab'); |
| assert.equal(tabs.length, 2); |
| assert.strictEqual(tabs[1].previousSibling, tabs[0].nextSibling); |
| assert.equal(tabs[0].previousSibling.textContent, |
| 'naπ’ti te, inquit, sumus aliquando otiosum, certe a '); |
| assert.equal(tabs[1].previousSibling.textContent, |
| 'udiam, quid sit, '); |
| assert.equal(tabs[1].nextSibling.textContent, 'quod Epicurum'); |
| }); |
| |
| test('merging multiple other hls', function() { |
| element._applyRangedHighlight('some', 140, 1, 140, 80, 'left'); |
| assert.instanceOf(content.firstChild, Text); |
| assert.equal(content.childNodes.length, 4); |
| var hl = content.querySelector('hl.some'); |
| assert.strictEqual(content.firstChild, hl.previousSibling); |
| assert.equal(hl.childNodes.length, 5); |
| assert.equal(content.querySelectorAll('span.tab').length, 2); |
| assert.equal(hl.textContent, |
| 'aπ’ti te, inquit, sumus aliquando otiosum, certe a udiam, ' + |
| 'quid sit, quod Epicuru'); |
| }); |
| |
| test('hl inside Text node', function() { |
| // Before: naπ’ti |
| // After: n<hl class="some">aπ’t</hl>i |
| element._applyRangedHighlight('some', 140, 1, 140, 4, 'left'); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.outerHTML, '<hl class="some">aπ’t</hl>'); |
| }); |
| |
| test('hl ending over different hl', function() { |
| // Before: naπ’ti <hl>te, inquit</hl>, |
| // After: naπ’<hl class="some">ti te</hl><hl class="foo">, inquit</hl>, |
| element._applyRangedHighlight('some', 140, 3, 140, 8, 'left'); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.outerHTML, '<hl class="some">ti te</hl>'); |
| assert.equal(hl.nextSibling.outerHTML, |
| '<hl class="foo">, inquit</hl>'); |
| }); |
| |
| test('hl starting inside different hl', function() { |
| // Before: naπ’ti <hl>te, inquit</hl>, sumus |
| // After: naπ’ti <hl class="foo">te, in</hl><hl class="some">quit, ... |
| element._applyRangedHighlight('some', 140, 12, 140, 21, 'left'); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.textContent, 'quit, sum'); |
| assert.equal( |
| hl.previousSibling.outerHTML, '<hl class="foo">te, in</hl>'); |
| }); |
| |
| test('hl inside different hl', function() { |
| // Before: naπ’ti <hl class="foo">te, inquit</hl>, sumus |
| // After: <hl class="foo">t</hl><hl="some">e, i</hl><hl class="foo">n.. |
| element._applyRangedHighlight('some', 140, 7, 140, 12, 'left'); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.textContent, 'e, in'); |
| assert.equal(hl.previousSibling.outerHTML, '<hl class="foo">t</hl>'); |
| assert.equal(hl.nextSibling.outerHTML, '<hl class="foo">quit</hl>'); |
| }); |
| |
| test('hl starts and ends in different hls', function() { |
| element._applyRangedHighlight('some', 140, 8, 140, 27, 'left'); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.textContent, ', inquit, sumus ali'); |
| assert.equal(hl.previousSibling.outerHTML, '<hl class="foo">te</hl>'); |
| assert.equal(hl.nextSibling.outerHTML, '<hl class="bar">quando</hl>'); |
| }); |
| |
| test('hl over different hl', function() { |
| element._applyRangedHighlight('some', 140, 2, 140, 21, 'left'); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.outerHTML, '<hl class="some">π’ti te, inquit, sum</hl>'); |
| assert.notOk(content.querySelector('.foo')); |
| }); |
| |
| test('hl starting and ending in boundaries', function() { |
| element._applyRangedHighlight('some', 140, 6, 140, 33, 'left'); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.textContent, 'te, inquit, sumus aliquando'); |
| assert.notOk(content.querySelector('.bar')); |
| }); |
| |
| test('overlapping hls', function() { |
| element._applyRangedHighlight('some', 140, 1, 140, 3, 'left'); |
| element._applyRangedHighlight('some', 140, 2, 140, 4, 'left'); |
| assert.equal(content.querySelectorAll('hl.some').length, 1); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.outerHTML, '<hl class="some">aπ’t</hl>'); |
| }); |
| |
| test('growing hl right including another hl', function() { |
| element._applyRangedHighlight('some', 140, 1, 140, 4, 'left'); |
| element._applyRangedHighlight('some', 140, 3, 140, 10, 'left'); |
| assert.equal(content.querySelectorAll('hl.some').length, 1); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.outerHTML, '<hl class="some">aπ’ti te, </hl>'); |
| assert.equal(hl.nextSibling.outerHTML, '<hl class="foo">inquit</hl>'); |
| }); |
| |
| test('growing hl left to start of line', function() { |
| element._applyRangedHighlight('some', 140, 2, 140, 5, 'left'); |
| element._applyRangedHighlight('some', 140, 0, 140, 3, 'left'); |
| assert.equal(content.querySelectorAll('hl.some').length, 1); |
| var hl = content.querySelector('hl.some'); |
| assert.equal(hl.outerHTML, '<hl class="some">naπ’ti</hl>'); |
| assert.strictEqual(content.firstChild, hl); |
| }); |
| |
| test('splitting hl containing a tab', function() { |
| element._applyRangedHighlight('some', 140, 63, 140, 72, 'left'); |
| assert.equal(content.querySelector('hl.some').textContent, 'sit, quod'); |
| element._applyRangedHighlight('another', 140, 66, 140, 81, 'left'); |
| assert.equal(content.querySelector('hl.another').textContent, |
| ', quod Epicurum'); |
| }); |
| }); |
| |
| test('_applyAllHighlights', function() { |
| element.comments = { |
| left: [ |
| { |
| range: { |
| start_line: 3, |
| start_character: 14, |
| end_line: 10, |
| end_character: 24, |
| }, |
| }, |
| ], |
| right: [ |
| { |
| range: { |
| start_line: 320, |
| start_character: 200, |
| end_line: 1024, |
| end_character: 768, |
| }, |
| }, |
| ], |
| }; |
| sandbox.stub(element, '_applyRangedHighlight'); |
| element._applyAllHighlights(); |
| sinon.assert.calledWith(element._applyRangedHighlight, |
| 'range', 3, 14, 10, 24, 'left'); |
| sinon.assert.calledWith(element._applyRangedHighlight, |
| 'range', 320, 200, 1024, 768, 'right'); |
| }); |
| |
| test('apply comment ranges on render', function() { |
| element.enabled = true; |
| sandbox.stub(element, '_applyAllHighlights'); |
| element.fire('render'); |
| assert.isTrue(element._applyAllHighlights.called); |
| }); |
| |
| test('apply comment ranges on context expand', function() { |
| element.enabled = true; |
| sandbox.stub(element, '_applyAllHighlights'); |
| element.fire('show-context'); |
| assert.isTrue(element._applyAllHighlights.called); |
| }); |
| |
| test('ignores render when disabled', function() { |
| element.enabled = false; |
| sandbox.stub(element, '_applyAllHighlights'); |
| element.fire('render'); |
| assert.isFalse(element._applyAllHighlights.called); |
| }); |
| |
| test('ignores context expand when disabled', function() { |
| element.enabled = false; |
| sandbox.stub(element, '_applyAllHighlights'); |
| element.fire('show-context'); |
| assert.isFalse(element._applyAllHighlights.called); |
| }); |
| |
| suite('selection', function() { |
| var diff; |
| var builder; |
| var contentStubs; |
| |
| var stubContent = function(line, side, opt_child) { |
| var content = diff.querySelector( |
| '.' + side + '.lineNum[data-value="' + line + '"] ~ .content'); |
| var lineEl = diff.querySelector( |
| '.' + side + '.lineNum[data-value="' + line + '"]'); |
| contentStubs.push({ |
| lineEl: lineEl, |
| content: content, |
| }); |
| builder.getContentByLineEl.withArgs(lineEl).returns(content); |
| builder.getLineNumberByChild.withArgs(lineEl).returns(line); |
| builder.getContentByLine.withArgs(line, side).returns(content); |
| builder.getSideByLineEl.withArgs(lineEl).returns(side); |
| return content; |
| }; |
| |
| var emulateSelection = function( |
| startNode, startOffset, endNode, endOffset) { |
| var selection = window.getSelection(); |
| var range = document.createRange(); |
| range.setStart(startNode, startOffset); |
| range.setEnd(endNode, endOffset); |
| selection.addRange(range); |
| element._handleSelection(); |
| }; |
| |
| var getActionRange = function() { |
| return Polymer.dom(element.root).querySelector( |
| 'gr-selection-action-box').range; |
| }; |
| |
| var getActionSide = function() { |
| return Polymer.dom(element.root).querySelector( |
| 'gr-selection-action-box').side; |
| }; |
| |
| var getLineElByChild = function(node) { |
| var stubs = contentStubs.find(function(stub) { |
| return stub.content.contains(node); |
| }); |
| return stubs && stubs.lineEl; |
| }; |
| |
| setup(function() { |
| contentStubs = []; |
| stub('gr-selection-action-box', { |
| placeAbove: sandbox.stub(), |
| }); |
| diff = element.querySelector('#diffTable'); |
| builder = { |
| getContentByLine: sandbox.stub(), |
| getContentByLineEl: sandbox.stub(), |
| getLineElByChild: getLineElByChild, |
| getLineNumberByChild: sandbox.stub(), |
| getSideByLineEl: sandbox.stub(), |
| }; |
| element._cachedDiffBuilder = builder; |
| element.enabled = true; |
| }); |
| |
| teardown(function() { |
| contentStubs = null; |
| window.getSelection().removeAllRanges(); |
| }); |
| |
| test('single line', function() { |
| var content = stubContent(138, 'left'); |
| emulateSelection(content.firstChild, 5, content.firstChild, 12); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 138, |
| startChar: 5, |
| endLine: 138, |
| endChar: 12, |
| }); |
| assert.equal(getActionSide(), 'left'); |
| }); |
| |
| test('multiline', function() { |
| var startContent = stubContent(119, 'right'); |
| var endContent = stubContent(120, 'right'); |
| emulateSelection( |
| startContent.firstChild, 10, endContent.lastChild, 7); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 119, |
| startChar: 10, |
| endLine: 120, |
| endChar: 34, |
| }); |
| assert.equal(getActionSide(), 'right'); |
| }); |
| |
| test('multiline grow end highlight over tabs', function() { |
| var startContent = stubContent(119, 'right'); |
| var endContent = stubContent(120, 'right'); |
| emulateSelection(startContent.firstChild, 10, endContent.firstChild, 2); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 119, |
| startChar: 10, |
| endLine: 120, |
| endChar: 2, |
| }); |
| assert.equal(getActionSide(), 'right'); |
| }); |
| |
| test('collapsed', function() { |
| var content = stubContent(138, 'left'); |
| emulateSelection(content.firstChild, 5, content.firstChild, 5); |
| assert.isOk(window.getSelection().getRangeAt(0).startContainer); |
| assert.isFalse(element.isRangeSelected()); |
| }); |
| |
| test('starts inside hl', function() { |
| var content = stubContent(140, 'left'); |
| var hl = content.querySelector('.foo'); |
| emulateSelection(hl.firstChild, 2, hl.nextSibling, 7); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 140, |
| startChar: 8, |
| endLine: 140, |
| endChar: 23, |
| }); |
| assert.equal(getActionSide(), 'left'); |
| }); |
| |
| test('ends inside hl', function() { |
| var content = stubContent(140, 'left'); |
| var hl = content.querySelector('.bar'); |
| emulateSelection(hl.previousSibling, 2, hl.firstChild, 3); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 140, |
| startChar: 18, |
| endLine: 140, |
| endChar: 27, |
| }); |
| }); |
| |
| test('multiple hl', function() { |
| var content = stubContent(140, 'left'); |
| var hl = content.querySelectorAll('hl')[4]; |
| emulateSelection(content.firstChild, 2, hl.firstChild, 2); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 140, |
| startChar: 2, |
| endLine: 140, |
| endChar: 60, |
| }); |
| assert.equal(getActionSide(), 'left'); |
| }); |
| |
| test('starts outside of diff', function() { |
| var content = stubContent(140, 'left'); |
| emulateSelection(content.previousElementSibling.firstChild, 2, |
| content.firstChild, 2); |
| assert.isFalse(element.isRangeSelected()); |
| }); |
| |
| test('ends outside of diff', function() { |
| var content = stubContent(140, 'left'); |
| emulateSelection(content.nextElementSibling.firstChild, 2, |
| content.firstChild, 2); |
| assert.isFalse(element.isRangeSelected()); |
| }); |
| |
| test('starts and ends on different sides', function() { |
| var startContent = stubContent(140, 'left'); |
| var endContent = stubContent(130, 'right'); |
| emulateSelection(startContent.firstChild, 2, endContent.firstChild, 2); |
| assert.isFalse(element.isRangeSelected()); |
| }); |
| |
| test('starts in comment thread element', function() { |
| var startContent = stubContent(140, 'left'); |
| var comment = startContent.querySelector('gr-diff-comment-thread'); |
| var endContent = stubContent(141, 'left'); |
| emulateSelection(comment.firstChild, 2, endContent.firstChild, 4); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 140, |
| startChar: 81, |
| endLine: 141, |
| endChar: 4, |
| }); |
| assert.equal(getActionSide(), 'left'); |
| }); |
| |
| test('ends in comment thread element', function() { |
| var content = stubContent(140, 'left'); |
| var comment = content.querySelector('gr-diff-comment-thread'); |
| emulateSelection(content.firstChild, 4, comment.firstChild, 1); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 140, |
| startChar: 4, |
| endLine: 140, |
| endChar: 81, |
| }); |
| assert.equal(getActionSide(), 'left'); |
| }); |
| |
| test('starts in context element', function() { |
| var contextControl = diff.querySelector('.contextControl'); |
| var content = stubContent(146, 'right'); |
| emulateSelection(contextControl, 0, content.firstChild, 7); |
| // TODO (viktard): Select nearest line. |
| assert.isFalse(element.isRangeSelected()); |
| }); |
| |
| test('ends in context element', function() { |
| var contextControl = diff.querySelector('.contextControl'); |
| var content = stubContent(141, 'left'); |
| emulateSelection(content.firstChild, 2, contextControl, 0); |
| // TODO (viktard): Select nearest line. |
| assert.isFalse(element.isRangeSelected()); |
| }); |
| |
| test('selection containing context element', function() { |
| var startContent = stubContent(130, 'right'); |
| var endContent = stubContent(146, 'right'); |
| emulateSelection(startContent.firstChild, 3, endContent.firstChild, 14); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 130, |
| startChar: 3, |
| endLine: 146, |
| endChar: 14, |
| }); |
| assert.equal(getActionSide(), 'right'); |
| }); |
| |
| test('ends at a tab', function() { |
| var content = stubContent(140, 'left'); |
| emulateSelection( |
| content.firstChild, 1, content.querySelector('span'), 0); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 140, |
| startChar: 1, |
| endLine: 140, |
| endChar: 51, |
| }); |
| assert.equal(getActionSide(), 'left'); |
| }); |
| |
| test('starts at a tab', function() { |
| var content = stubContent(140, 'left'); |
| emulateSelection( |
| content.querySelectorAll('hl')[3], 0, |
| content.querySelectorAll('span')[1], 0); |
| assert.isTrue(element.isRangeSelected()); |
| assert.deepEqual(getActionRange(), { |
| startLine: 140, |
| startChar: 51, |
| endLine: 140, |
| endChar: 68, |
| }); |
| assert.equal(getActionSide(), 'left'); |
| }); |
| |
| // TODO (viktard): Selection starts in line number. |
| // TODO (viktard): Empty lines in selection start. |
| // TODO (viktard): Empty lines in selection end. |
| // TODO (viktard): Only empty lines selected. |
| // TODO (viktard): Unified mode. |
| }); |
| }); |
| </script> |