At the time that syntax highlighting DOM was introduced, the offsets of selection ranges had been broken. In change [1] Kasper fixed this for GR-DIFF-HIGHLIGHT with selection normalization functions. However, selections for copying code as implemented in GR-DIFF-SELECTION were still un-normalized. With this change, the normalization functionality introduced in [1] is moved to a JS library so that it can be used by both components. Tests are updated. [1] I26c61ca706575ea5df6e3b7b18a27225834396e8 Change-Id: I35ab0f71a46b3fc1d7356a314a0cae856f2ef28e
		
			
				
	
	
		
			519 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			519 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!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"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
 | 
						|
            <td class="right lineNum" data-value="119">119</td>
 | 
						|
            <td class="content both darkHighlight"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></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"><div class="contentText">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></div><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"><div class="contentText">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</div></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"><div class="contentText">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;</div></td>
 | 
						|
            <td class="right lineNum" data-value="130"></td>
 | 
						|
            <td class="content both darkHighlight"><div class="contentText">nam et complectitur verbis, quod vult, et dicit plane, quod intellegam;</div></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"><div class="contentText">[17] Quid igitur est? inquit; audire enim cupio, quid non probes. Principio, inquam,</div></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"><div class="contentText">in physicis, quibus maxime gloriatur, primum totus est alienus. Democritea dicit</div></td>
 | 
						|
            <td class="right lineNum" data-value="147"></td>
 | 
						|
            <td class="content both darkHighlight"><div class="contentText">in physicis, <hl><span class="tab withIndicator" style="tab-size:8;">	</span></hl> quibus maxime gloriatur, primum totus est alienus. Democritea dicit</div></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();
 | 
						|
    });
 | 
						|
 | 
						|
    suite('selectionchange event handling', function() {
 | 
						|
      var emulateSelection = function() {
 | 
						|
        document.dispatchEvent(new CustomEvent('selectionchange'));
 | 
						|
        element.flushDebouncer('selectionChange');
 | 
						|
        element.flushDebouncer('removeActionBox');
 | 
						|
      };
 | 
						|
 | 
						|
      setup(function() {
 | 
						|
        sandbox.stub(element, '_handleSelection');
 | 
						|
        sandbox.stub(element, '_removeActionBox');
 | 
						|
      });
 | 
						|
 | 
						|
      test('enabled if logged in', function() {
 | 
						|
        element.loggedIn = true;
 | 
						|
        emulateSelection();
 | 
						|
        assert.isTrue(element._handleSelection.called);
 | 
						|
        assert.isTrue(element._removeActionBox.called);
 | 
						|
      });
 | 
						|
 | 
						|
      test('ignored if logged out', function() {
 | 
						|
        element.loggedIn = false;
 | 
						|
        emulateSelection();
 | 
						|
        assert.isFalse(element._handleSelection.called);
 | 
						|
        assert.isFalse(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;
 | 
						|
      });
 | 
						|
 | 
						|
      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('comment-mouse-over from line comments is ignored', function() {
 | 
						|
        sandbox.stub(element, 'set');
 | 
						|
        element.fire('comment-mouse-over', {comment: {}});
 | 
						|
        assert.isFalse(element.set.called);
 | 
						|
      });
 | 
						|
 | 
						|
      test('comment-mouse-over from ranged comment causes set', function() {
 | 
						|
        sandbox.stub(element, 'set');
 | 
						|
        sandbox.stub(element, '_indexOfComment').returns(0);
 | 
						|
        element.fire('comment-mouse-over', {comment: {range: {}}});
 | 
						|
        assert.isTrue(element.set.called);
 | 
						|
      });
 | 
						|
 | 
						|
      test('comment-mouse-out from line comments is ignored', function() {
 | 
						|
        element.fire('comment-mouse-over', {comment: {}});
 | 
						|
        assert.isFalse(builder.getContentsByLineRange.called);
 | 
						|
      });
 | 
						|
 | 
						|
      test('on create-comment action box is removed', function() {
 | 
						|
        sandbox.stub(element, '_removeActionBox');
 | 
						|
        element.fire('create-comment', {
 | 
						|
          comment: {
 | 
						|
            range: {},
 | 
						|
          },
 | 
						|
        });
 | 
						|
        assert.isTrue(element._removeActionBox.called);
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    suite('selection', function() {
 | 
						|
      var diff;
 | 
						|
      var builder;
 | 
						|
      var contentStubs;
 | 
						|
 | 
						|
      var stubContent = function(line, side, opt_child) {
 | 
						|
        var contentTd = diff.querySelector(
 | 
						|
            '.' + side + '.lineNum[data-value="' + line + '"] ~ .content');
 | 
						|
        var contentText = contentTd.querySelector('.contentText');
 | 
						|
        var lineEl = diff.querySelector(
 | 
						|
            '.' + side + '.lineNum[data-value="' + line + '"]');
 | 
						|
        contentStubs.push({
 | 
						|
          lineEl: lineEl,
 | 
						|
          contentTd: contentTd,
 | 
						|
          contentText: contentText,
 | 
						|
        });
 | 
						|
        builder.getContentByLineEl.withArgs(lineEl).returns(contentText);
 | 
						|
        builder.getLineNumberByChild.withArgs(lineEl).returns(line);
 | 
						|
        builder.getContentByLine.withArgs(line, side).returns(contentText);
 | 
						|
        builder.getSideByLineEl.withArgs(lineEl).returns(side);
 | 
						|
        return contentText;
 | 
						|
      };
 | 
						|
 | 
						|
      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.contentTd.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;
 | 
						|
      });
 | 
						|
 | 
						|
      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 contentText = stubContent(140, 'left');
 | 
						|
        var contentTd = contentText.parentElement;
 | 
						|
 | 
						|
        emulateSelection(contentTd.previousElementSibling.firstChild, 2,
 | 
						|
            contentText.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.parentElement.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.parentElement.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');
 | 
						|
      });
 | 
						|
 | 
						|
      test('properly accounts for syntax highlighting', function() {
 | 
						|
        var content = stubContent(140, 'left');
 | 
						|
        var spy = sinon.spy(element, '_normalizeRange');
 | 
						|
        emulateSelection(
 | 
						|
            content.querySelectorAll('hl')[3], 0,
 | 
						|
            content.querySelectorAll('span')[1], 0);
 | 
						|
        var spyCall = spy.getCall(0);
 | 
						|
        var range = window.getSelection().getRangeAt(0);
 | 
						|
        assert.notDeepEqual(spyCall.returnValue, range);
 | 
						|
      });
 | 
						|
 | 
						|
      test('GrRangeNormalizer._getTextOffset computes text offset', function() {
 | 
						|
        var content = stubContent(140, 'left');
 | 
						|
        var child = content.lastChild.lastChild;
 | 
						|
        var result = GrRangeNormalizer._getTextOffset(content, child);
 | 
						|
        assert.equal(result, 73);
 | 
						|
        content = stubContent(146, 'right');
 | 
						|
        child = content.lastChild;
 | 
						|
        result = GrRangeNormalizer._getTextOffset(content, child);
 | 
						|
        assert.equal(result, 0);
 | 
						|
      });
 | 
						|
      // 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>
 |