Merge "Cut dep from highlight onto comment model"
This commit is contained in:
		@@ -29,7 +29,7 @@ limitations under the License.
 | 
			
		||||
    </div>
 | 
			
		||||
    <gr-ranged-comment-layer
 | 
			
		||||
        id="rangeLayer"
 | 
			
		||||
        comments="[[comments]]"></gr-ranged-comment-layer>
 | 
			
		||||
        comment-ranges="[[commentRanges]]"></gr-ranged-comment-layer>
 | 
			
		||||
    <gr-syntax-layer
 | 
			
		||||
        id="syntaxLayer"
 | 
			
		||||
        diff="[[diff]]"></gr-syntax-layer>
 | 
			
		||||
@@ -109,7 +109,6 @@ limitations under the License.
 | 
			
		||||
          changeNum: String,
 | 
			
		||||
          patchNum: String,
 | 
			
		||||
          viewMode: String,
 | 
			
		||||
          comments: Object,
 | 
			
		||||
          isImageDiff: Boolean,
 | 
			
		||||
          baseImage: Object,
 | 
			
		||||
          revisionImage: Object,
 | 
			
		||||
@@ -125,6 +124,10 @@ limitations under the License.
 | 
			
		||||
          _groups: Array,
 | 
			
		||||
          _layers: Array,
 | 
			
		||||
          _showTabs: Boolean,
 | 
			
		||||
          /** @type {!Array<!Gerrit.HoveredRange>} */
 | 
			
		||||
          commentRanges: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        get diffElement() {
 | 
			
		||||
 
 | 
			
		||||
@@ -389,7 +389,7 @@ limitations under the License.
 | 
			
		||||
    test('_handlePreferenceError called with invalid preference', () => {
 | 
			
		||||
      sandbox.stub(element, '_handlePreferenceError');
 | 
			
		||||
      const prefs = {tab_size: 0};
 | 
			
		||||
      element._getDiffBuilder(element.diff, element.comments, prefs);
 | 
			
		||||
      element._getDiffBuilder(element.diff, undefined, prefs);
 | 
			
		||||
      assert.isTrue(element._handlePreferenceError.lastCall
 | 
			
		||||
          .calledWithExactly('tab size'));
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,11 @@
 | 
			
		||||
    is: 'gr-diff-highlight',
 | 
			
		||||
 | 
			
		||||
    properties: {
 | 
			
		||||
      comments: Object,
 | 
			
		||||
      /** @type {!Array<!Gerrit.HoveredRange>} */
 | 
			
		||||
      commentRanges: {
 | 
			
		||||
        type: Array,
 | 
			
		||||
        notify: true,
 | 
			
		||||
      },
 | 
			
		||||
      loggedIn: Boolean,
 | 
			
		||||
      /**
 | 
			
		||||
       * querySelector can return null, so needs to be nullable.
 | 
			
		||||
@@ -71,35 +75,44 @@
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _handleCommentMouseOver(e) {
 | 
			
		||||
      const comment = e.detail.comment;
 | 
			
		||||
      if (!comment.range) { return; }
 | 
			
		||||
      const lineEl = this.diffBuilder.getLineElByChild(e.target);
 | 
			
		||||
      const side = this.diffBuilder.getSideByLineEl(lineEl);
 | 
			
		||||
      const index = this._indexOfComment(side, comment);
 | 
			
		||||
      const threadEl = Polymer.dom(e).localTarget;
 | 
			
		||||
      const index = this._indexForThreadEl(threadEl);
 | 
			
		||||
 | 
			
		||||
      if (index !== undefined) {
 | 
			
		||||
        this.set(['comments', side, index, '__hovering'], true);
 | 
			
		||||
        this.set(['commentRanges', index, 'hovering'], true);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _handleCommentMouseOut(e) {
 | 
			
		||||
      const comment = e.detail.comment;
 | 
			
		||||
      if (!comment.range) { return; }
 | 
			
		||||
      const lineEl = this.diffBuilder.getLineElByChild(e.target);
 | 
			
		||||
      const side = this.diffBuilder.getSideByLineEl(lineEl);
 | 
			
		||||
      const index = this._indexOfComment(side, comment);
 | 
			
		||||
      const threadEl = Polymer.dom(e).localTarget;
 | 
			
		||||
      const index = this._indexForThreadEl(threadEl);
 | 
			
		||||
 | 
			
		||||
      if (index !== undefined) {
 | 
			
		||||
        this.set(['comments', side, index, '__hovering'], false);
 | 
			
		||||
        this.set(['commentRanges', index, 'hovering'], false);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _indexOfComment(side, comment) {
 | 
			
		||||
      const idProp = comment.id ? 'id' : '__draftID';
 | 
			
		||||
      for (let i = 0; i < this.comments[side].length; i++) {
 | 
			
		||||
        if (comment[idProp] &&
 | 
			
		||||
            this.comments[side][i][idProp] === comment[idProp]) {
 | 
			
		||||
          return i;
 | 
			
		||||
        }
 | 
			
		||||
    _indexForThreadEl(threadEl) {
 | 
			
		||||
      const side = threadEl.getAttribute('comment-side');
 | 
			
		||||
      const range = JSON.parse(threadEl.getAttribute('range'));
 | 
			
		||||
 | 
			
		||||
      if (!range) return undefined;
 | 
			
		||||
 | 
			
		||||
      return this._indexOfCommentRange(side, range);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _indexOfCommentRange(side, range) {
 | 
			
		||||
      function rangesEqual(a, b) {
 | 
			
		||||
        if (!a && !b) { return true; }
 | 
			
		||||
        if (!a || !b) { return false; }
 | 
			
		||||
        return a.start_line === b.start_line &&
 | 
			
		||||
            a.start_character === b.start_character &&
 | 
			
		||||
            a.end_line === b.end_line &&
 | 
			
		||||
            a.end_character === b.end_character;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return this.commentRanges.findIndex(commentRange =>
 | 
			
		||||
          commentRange.side === side && rangesEqual(commentRange.range, range));
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -205,7 +205,7 @@ limitations under the License.
 | 
			
		||||
 | 
			
		||||
      test('comment-mouse-over from ranged comment causes set', () => {
 | 
			
		||||
        sandbox.stub(element, 'set');
 | 
			
		||||
        sandbox.stub(element, '_indexOfComment').returns(0);
 | 
			
		||||
        sandbox.stub(element, '_indexForThreadEl').returns(0);
 | 
			
		||||
        element.fire('comment-mouse-over', {comment: {range: {}}});
 | 
			
		||||
        assert.isTrue(element.set.called);
 | 
			
		||||
      });
 | 
			
		||||
 
 | 
			
		||||
@@ -280,10 +280,10 @@ limitations under the License.
 | 
			
		||||
        <gr-diff-highlight
 | 
			
		||||
            id="highlights"
 | 
			
		||||
            logged-in="[[loggedIn]]"
 | 
			
		||||
            comments="{{comments}}">
 | 
			
		||||
            comment-ranges="{{_commentRanges}}">
 | 
			
		||||
          <gr-diff-builder
 | 
			
		||||
              id="diffBuilder"
 | 
			
		||||
              comments="[[comments]]"
 | 
			
		||||
              comment-ranges="[[_commentRanges]]"
 | 
			
		||||
              project-name="[[projectName]]"
 | 
			
		||||
              diff="[[diff]]"
 | 
			
		||||
              diff-path="[[path]]"
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,11 @@
 | 
			
		||||
   *             end_line: number, end_character: number}} */
 | 
			
		||||
  Gerrit.Range;
 | 
			
		||||
 | 
			
		||||
  function isThreadEl(node) {
 | 
			
		||||
    return node.nodeType === Node.ELEMENT_NODE &&
 | 
			
		||||
        node.classList.contains('comment-thread');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Polymer({
 | 
			
		||||
    is: 'gr-diff',
 | 
			
		||||
 | 
			
		||||
@@ -100,6 +105,11 @@
 | 
			
		||||
        type: Object,
 | 
			
		||||
        value: {left: [], right: []},
 | 
			
		||||
      },
 | 
			
		||||
      /** @type {!Array<!Gerrit.HoveredRange>} */
 | 
			
		||||
      _commentRanges: {
 | 
			
		||||
        type: Array,
 | 
			
		||||
        value: [],
 | 
			
		||||
      },
 | 
			
		||||
      lineWrapping: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        value: false,
 | 
			
		||||
@@ -185,7 +195,19 @@
 | 
			
		||||
 | 
			
		||||
      _diffLength: Number,
 | 
			
		||||
 | 
			
		||||
      /** @type {?PolymerDomApi.ObserveHandle} */
 | 
			
		||||
      /**
 | 
			
		||||
       * Observes comment nodes added or removed after the initial render.
 | 
			
		||||
       * Can be used to unregister when the entire diff is (re-)rendered or upon
 | 
			
		||||
       * detachment.
 | 
			
		||||
       * @type {?PolymerDomApi.ObserveHandle}
 | 
			
		||||
       */
 | 
			
		||||
      _incrementalNodeObserver: Object,
 | 
			
		||||
 | 
			
		||||
      /**
 | 
			
		||||
       * Observes comment nodes added or removed at any point.
 | 
			
		||||
       * Can be used to unregister upon detachment.
 | 
			
		||||
       * @type {?PolymerDomApi.ObserveHandle}
 | 
			
		||||
       */
 | 
			
		||||
      _nodeObserver: Object,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@@ -201,10 +223,36 @@
 | 
			
		||||
      'render-content': '_handleRenderContent',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    attached() {
 | 
			
		||||
      this._updateRangesWhenNodesChange();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    detached() {
 | 
			
		||||
      this._unobserveIncrementalNodes();
 | 
			
		||||
      this._unobserveNodes();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _updateRangesWhenNodesChange() {
 | 
			
		||||
      function commentRangeFromThreadEl(threadEl) {
 | 
			
		||||
        const side = threadEl.getAttribute('comment-side');
 | 
			
		||||
        const range = JSON.parse(threadEl.getAttribute('range'));
 | 
			
		||||
        return {side, range, hovering: false};
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this._nodeObserver = Polymer.dom(this).observeNodes(info => {
 | 
			
		||||
        const addedThreadEls = info.addedNodes.filter(isThreadEl);
 | 
			
		||||
        const addedCommentRanges = addedThreadEls
 | 
			
		||||
            .map(commentRangeFromThreadEl)
 | 
			
		||||
            .filter(({range}) => range);
 | 
			
		||||
        this.push('_commentRanges', ...addedCommentRanges);
 | 
			
		||||
        // In principal we should also handle removed nodes, but I have not
 | 
			
		||||
        // figured out how to do that yet without also catching all the removals
 | 
			
		||||
        // caused by further redistribution. Right now, comments are never
 | 
			
		||||
        // removed by no longer slotting them in, so I decided to not handle
 | 
			
		||||
        // this situation until it occurs.
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /** Cancel any remaining diff builder rendering work. */
 | 
			
		||||
    cancel() {
 | 
			
		||||
      this.$.diffBuilder.cancel();
 | 
			
		||||
@@ -577,7 +625,7 @@
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _renderDiffTable() {
 | 
			
		||||
      this._unobserveNodes();
 | 
			
		||||
      this._unobserveIncrementalNodes();
 | 
			
		||||
      if (!this.prefs) {
 | 
			
		||||
        this.dispatchEvent(new CustomEvent('render', {bubbles: true}));
 | 
			
		||||
        return;
 | 
			
		||||
@@ -595,9 +643,8 @@
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _handleRenderContent() {
 | 
			
		||||
      this._nodeObserver = Polymer.dom(this).observeNodes(info => {
 | 
			
		||||
        const addedThreadEls = info.addedNodes.filter(
 | 
			
		||||
            node => node.nodeType === Node.ELEMENT_NODE);
 | 
			
		||||
      this._incrementalNodeObserver = Polymer.dom(this).observeNodes(info => {
 | 
			
		||||
        const addedThreadEls = info.addedNodes.filter(isThreadEl);
 | 
			
		||||
        // In principal we should also handle removed nodes, but I have not
 | 
			
		||||
        // figured out how to do that yet without also catching all the removals
 | 
			
		||||
        // caused by further redistribution. Right now, comments are never
 | 
			
		||||
@@ -616,6 +663,12 @@
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _unobserveIncrementalNodes() {
 | 
			
		||||
      if (this._incrementalNodeObserver) {
 | 
			
		||||
        Polymer.dom(this).unobserveNodes(this._incrementalNodeObserver);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _unobserveNodes() {
 | 
			
		||||
      if (this._nodeObserver) {
 | 
			
		||||
        Polymer.dom(this).unobserveNodes(this._nodeObserver);
 | 
			
		||||
@@ -633,7 +686,7 @@
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    clearDiffContent() {
 | 
			
		||||
      this._unobserveNodes();
 | 
			
		||||
      this._unobserveIncrementalNodes();
 | 
			
		||||
      this.$.diffTable.innerHTML = null;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,31 +17,34 @@
 | 
			
		||||
(function() {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  const HOVER_PATH_PATTERN = /^comments\.(left|right)\.\#(\d+)\.__hovering$/;
 | 
			
		||||
  const SPLICE_PATH_PATTERN = /^comments\.(left|right)\.splices$/;
 | 
			
		||||
  const HOVER_PATH_PATTERN = /^commentRanges\.\#(\d+)\.hovering$/;
 | 
			
		||||
 | 
			
		||||
  const RANGE_HIGHLIGHT = 'range';
 | 
			
		||||
  const HOVER_HIGHLIGHT = 'rangeHighlight';
 | 
			
		||||
 | 
			
		||||
  const NORMALIZE_RANGE_EVENT = 'normalize-range';
 | 
			
		||||
 | 
			
		||||
  /** @typedef {{side: string, range: Gerrit.Range, hovering: boolean}} */
 | 
			
		||||
  Gerrit.HoveredRange;
 | 
			
		||||
 | 
			
		||||
  Polymer({
 | 
			
		||||
    is: 'gr-ranged-comment-layer',
 | 
			
		||||
 | 
			
		||||
    properties: {
 | 
			
		||||
      comments: Object,
 | 
			
		||||
      /** @type {!Array<!Gerrit.HoveredRange>} */
 | 
			
		||||
      commentRanges: Array,
 | 
			
		||||
      _listeners: {
 | 
			
		||||
        type: Array,
 | 
			
		||||
        value() { return []; },
 | 
			
		||||
      },
 | 
			
		||||
      _commentMap: {
 | 
			
		||||
      _rangesMap: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        value() { return {left: [], right: []}; },
 | 
			
		||||
        value() { return {left: {}, right: {}}; },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    observers: [
 | 
			
		||||
      '_handleCommentChange(comments.*)',
 | 
			
		||||
      '_handleCommentRangesChange(commentRanges.*)',
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -93,97 +96,78 @@
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle change in the comments by updating the comment maps and by
 | 
			
		||||
     * Handle change in the ranges by updating the ranges maps and by
 | 
			
		||||
     * emitting appropriate update notifications.
 | 
			
		||||
     * @param {Object} record The change record.
 | 
			
		||||
     */
 | 
			
		||||
    _handleCommentChange(record) {
 | 
			
		||||
      if (!record.path) { return; }
 | 
			
		||||
    _handleCommentRangesChange(record) {
 | 
			
		||||
      if (!record) return;
 | 
			
		||||
 | 
			
		||||
      // If the entire set of comments was changed.
 | 
			
		||||
      if (record.path === 'comments') {
 | 
			
		||||
        this._commentMap.left = this._computeCommentMap(this.comments.left);
 | 
			
		||||
        this._commentMap.right = this._computeCommentMap(this.comments.right);
 | 
			
		||||
        return;
 | 
			
		||||
      if (record.path === 'commentRanges') {
 | 
			
		||||
        this._rangesMap = {left: {}, right: {}};
 | 
			
		||||
        for (const {side, range, hovering} of record.value) {
 | 
			
		||||
          this._updateRangesMap(
 | 
			
		||||
              side, range, hovering, (forLine, start, end, hovering) => {
 | 
			
		||||
                forLine.push({start, end, hovering});
 | 
			
		||||
              });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // If the change only changed the `hovering` property of a comment.
 | 
			
		||||
      let match = record.path.match(HOVER_PATH_PATTERN);
 | 
			
		||||
      let side;
 | 
			
		||||
 | 
			
		||||
      const match = record.path.match(HOVER_PATH_PATTERN);
 | 
			
		||||
      if (match) {
 | 
			
		||||
        side = match[1];
 | 
			
		||||
        const index = match[2];
 | 
			
		||||
        const comment = this.comments[side][index];
 | 
			
		||||
        if (comment && comment.range) {
 | 
			
		||||
          this._commentMap[side] = this._computeCommentMap(this.comments[side]);
 | 
			
		||||
          this._notifyUpdateRange(
 | 
			
		||||
              comment.range.start_line, comment.range.end_line, side);
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
        const commentRangesIndex = match[1];
 | 
			
		||||
        const {side, range, hovering} = this.commentRanges[commentRangesIndex];
 | 
			
		||||
        this._updateRangesMap(
 | 
			
		||||
            side, range, hovering, (forLine, start, end, hovering) => {
 | 
			
		||||
              const index = forLine.findIndex(lineRange =>
 | 
			
		||||
                  lineRange.start === start && lineRange.end === end);
 | 
			
		||||
              forLine[index].hovering = hovering;
 | 
			
		||||
            });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // If comments were spliced in or out.
 | 
			
		||||
      match = record.path.match(SPLICE_PATH_PATTERN);
 | 
			
		||||
      if (match) {
 | 
			
		||||
        side = match[1];
 | 
			
		||||
        this._commentMap[side] = this._computeCommentMap(this.comments[side]);
 | 
			
		||||
        this._handleCommentSplice(record.value, side);
 | 
			
		||||
      if (record.path === 'commentRanges.splices') {
 | 
			
		||||
        for (const indexSplice of record.value.indexSplices) {
 | 
			
		||||
          const removed = indexSplice.removed;
 | 
			
		||||
          for (const {side, range, hovering} of removed) {
 | 
			
		||||
            this._updateRangesMap(
 | 
			
		||||
                side, range, hovering, (forLine, start, end) => {
 | 
			
		||||
                  const index = forLine.findIndex(lineRange =>
 | 
			
		||||
                      lineRange.start === start && lineRange.end === end);
 | 
			
		||||
                  forLine.splice(index, 1);
 | 
			
		||||
                });
 | 
			
		||||
          }
 | 
			
		||||
          const added = indexSplice.object.slice(
 | 
			
		||||
              indexSplice.index, indexSplice.index + indexSplice.addedCount);
 | 
			
		||||
          for (const {side, range, hovering} of added) {
 | 
			
		||||
            this._updateRangesMap(
 | 
			
		||||
                side, range, hovering, (forLine, start, end, hovering) => {
 | 
			
		||||
                  forLine.push({start, end, hovering});
 | 
			
		||||
                });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Take a list of comments and return a sparse list mapping line numbers to
 | 
			
		||||
     * partial ranges. Uses an end-character-index of -1 to indicate the end of
 | 
			
		||||
     * the line.
 | 
			
		||||
     * @param {?} commentList The list of comments.
 | 
			
		||||
     *    Getting this param to match closure requirements caused problems.
 | 
			
		||||
     * @return {!Object} The sparse list.
 | 
			
		||||
     */
 | 
			
		||||
    _computeCommentMap(commentList) {
 | 
			
		||||
      const result = {};
 | 
			
		||||
      for (const comment of commentList) {
 | 
			
		||||
        if (!comment.range) { continue; }
 | 
			
		||||
        const range = comment.range;
 | 
			
		||||
        for (let line = range.start_line; line <= range.end_line; line++) {
 | 
			
		||||
          if (!result[line]) { result[line] = []; }
 | 
			
		||||
          result[line].push({
 | 
			
		||||
            comment,
 | 
			
		||||
            start: line === range.start_line ? range.start_character : 0,
 | 
			
		||||
            end: line === range.end_line ? range.end_character : -1,
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return result;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Translate a splice record into range update notifications.
 | 
			
		||||
     */
 | 
			
		||||
    _handleCommentSplice(record, side) {
 | 
			
		||||
      if (!record || !record.indexSplices) { return; }
 | 
			
		||||
 | 
			
		||||
      for (const splice of record.indexSplices) {
 | 
			
		||||
        const ranges = splice.removed.length ?
 | 
			
		||||
            splice.removed.map(c => { return c.range; }) :
 | 
			
		||||
            [splice.object[splice.index].range];
 | 
			
		||||
        for (const range of ranges) {
 | 
			
		||||
          if (!range) { continue; }
 | 
			
		||||
          this._notifyUpdateRange(range.start_line, range.end_line, side);
 | 
			
		||||
        }
 | 
			
		||||
    _updateRangesMap(side, range, hovering, operation) {
 | 
			
		||||
      const forSide = this._rangesMap[side] || (this._rangesMap[side] = {});
 | 
			
		||||
      for (let line = range.start_line; line <= range.end_line; line++) {
 | 
			
		||||
        const forLine = forSide[line] || (forSide[line] = []);
 | 
			
		||||
        const start = line === range.start_line ? range.start_character : 0;
 | 
			
		||||
        const end = line === range.end_line ? range.end_character : -1;
 | 
			
		||||
        operation(forLine, start, end, hovering);
 | 
			
		||||
      }
 | 
			
		||||
      this._notifyUpdateRange(range.start_line, range.end_line, side);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _getRangesForLine(line, side) {
 | 
			
		||||
      const lineNum = side === 'left' ? line.beforeNumber : line.afterNumber;
 | 
			
		||||
      const ranges = this.get(['_commentMap', side, lineNum]) || [];
 | 
			
		||||
      const ranges = this.get(['_rangesMap', side, lineNum]) || [];
 | 
			
		||||
      return ranges
 | 
			
		||||
          .map(range => {
 | 
			
		||||
            range = {
 | 
			
		||||
              start: range.start,
 | 
			
		||||
              end: range.end === -1 ? line.text.length : range.end,
 | 
			
		||||
              hovering: !!range.comment.__hovering,
 | 
			
		||||
            };
 | 
			
		||||
            range.end = range.end === -1 ? line.text.length : range.end;
 | 
			
		||||
 | 
			
		||||
            // Normalize invalid ranges where the start is after the end but the
 | 
			
		||||
            // start still makes sense. Set the end to the end of the line.
 | 
			
		||||
 
 | 
			
		||||
@@ -40,62 +40,48 @@ limitations under the License.
 | 
			
		||||
    let sandbox;
 | 
			
		||||
 | 
			
		||||
    setup(() => {
 | 
			
		||||
      const initialComments = {
 | 
			
		||||
        left: [
 | 
			
		||||
          {
 | 
			
		||||
            id: '12345',
 | 
			
		||||
            line: 39,
 | 
			
		||||
            message: 'range comment',
 | 
			
		||||
            range: {
 | 
			
		||||
              end_character: 9,
 | 
			
		||||
              end_line: 39,
 | 
			
		||||
              start_character: 6,
 | 
			
		||||
              start_line: 36,
 | 
			
		||||
            },
 | 
			
		||||
          }, {
 | 
			
		||||
            id: '23456',
 | 
			
		||||
            line: 100,
 | 
			
		||||
            message: 'non range comment',
 | 
			
		||||
      const initialCommentRanges = [
 | 
			
		||||
        {
 | 
			
		||||
          side: 'left',
 | 
			
		||||
          range: {
 | 
			
		||||
            end_character: 9,
 | 
			
		||||
            end_line: 39,
 | 
			
		||||
            start_character: 6,
 | 
			
		||||
            start_line: 36,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        right: [
 | 
			
		||||
          {
 | 
			
		||||
            id: '34567',
 | 
			
		||||
            line: 10,
 | 
			
		||||
            message: 'range comment',
 | 
			
		||||
            range: {
 | 
			
		||||
              end_character: 22,
 | 
			
		||||
              end_line: 12,
 | 
			
		||||
              start_character: 10,
 | 
			
		||||
              start_line: 10,
 | 
			
		||||
            },
 | 
			
		||||
          }, {
 | 
			
		||||
            id: '45678',
 | 
			
		||||
            line: 100,
 | 
			
		||||
            message: 'single line range comment',
 | 
			
		||||
            range: {
 | 
			
		||||
              end_character: 15,
 | 
			
		||||
              end_line: 100,
 | 
			
		||||
              start_character: 5,
 | 
			
		||||
              start_line: 100,
 | 
			
		||||
            },
 | 
			
		||||
          }, {
 | 
			
		||||
            id: '8675309',
 | 
			
		||||
            line: 55,
 | 
			
		||||
            message: 'nonsense range',
 | 
			
		||||
            range: {
 | 
			
		||||
              end_character: 2,
 | 
			
		||||
              end_line: 55,
 | 
			
		||||
              start_character: 32,
 | 
			
		||||
              start_line: 55,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          side: 'right',
 | 
			
		||||
          range: {
 | 
			
		||||
            end_character: 22,
 | 
			
		||||
            end_line: 12,
 | 
			
		||||
            start_character: 10,
 | 
			
		||||
            start_line: 10,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      };
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          side: 'right',
 | 
			
		||||
          range: {
 | 
			
		||||
            end_character: 15,
 | 
			
		||||
            end_line: 100,
 | 
			
		||||
            start_character: 5,
 | 
			
		||||
            start_line: 100,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          side: 'right',
 | 
			
		||||
          range: {
 | 
			
		||||
            end_character: 2,
 | 
			
		||||
            end_line: 55,
 | 
			
		||||
            start_character: 32,
 | 
			
		||||
            start_line: 55,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      sandbox = sinon.sandbox.create();
 | 
			
		||||
      element = fixture('basic');
 | 
			
		||||
      element.comments = initialComments;
 | 
			
		||||
      element.commentRanges = initialCommentRanges;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    teardown(() => {
 | 
			
		||||
@@ -149,7 +135,7 @@ limitations under the License.
 | 
			
		||||
      test('type=Remove has-comment hovering', () => {
 | 
			
		||||
        line.type = GrDiffLine.Type.REMOVE;
 | 
			
		||||
        line.beforeNumber = 36;
 | 
			
		||||
        element.set(['comments', 'left', 0, '__hovering'], true);
 | 
			
		||||
        element.set(['commentRanges', 0, 'hovering'], true);
 | 
			
		||||
 | 
			
		||||
        const expectedStart = 6;
 | 
			
		||||
        const expectedLength = line.text.length - expectedStart;
 | 
			
		||||
@@ -210,29 +196,18 @@ limitations under the License.
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('_handleCommentChange overwrite', () => {
 | 
			
		||||
      const handlerSpy = sandbox.spy(element, '_handleCommentChange');
 | 
			
		||||
      const mapSpy = sandbox.spy(element, '_computeCommentMap');
 | 
			
		||||
    test('_handleCommentRangesChange overwrite', () => {
 | 
			
		||||
      element.set('commentRanges', []);
 | 
			
		||||
 | 
			
		||||
      element.set('comments', {left: [], right: []});
 | 
			
		||||
 | 
			
		||||
      assert.isTrue(handlerSpy.called);
 | 
			
		||||
      assert.equal(mapSpy.callCount, 2);
 | 
			
		||||
 | 
			
		||||
      assert.equal(Object.keys(element._commentMap.left).length, 0);
 | 
			
		||||
      assert.equal(Object.keys(element._commentMap.right).length, 0);
 | 
			
		||||
      assert.equal(Object.keys(element._rangesMap.left).length, 0);
 | 
			
		||||
      assert.equal(Object.keys(element._rangesMap.right).length, 0);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('_handleCommentChange hovering', () => {
 | 
			
		||||
      const handlerSpy = sandbox.spy(element, '_handleCommentChange');
 | 
			
		||||
      const mapSpy = sandbox.spy(element, '_computeCommentMap');
 | 
			
		||||
    test('_handleCommentRangesChange hovering', () => {
 | 
			
		||||
      const notifyStub = sinon.stub();
 | 
			
		||||
      element.addListener(notifyStub);
 | 
			
		||||
 | 
			
		||||
      element.set(['comments', 'right', 0, '__hovering'], true);
 | 
			
		||||
 | 
			
		||||
      assert.isTrue(handlerSpy.called);
 | 
			
		||||
      assert.isTrue(mapSpy.called);
 | 
			
		||||
      element.set(['commentRanges', 1, 'hovering'], true);
 | 
			
		||||
 | 
			
		||||
      assert.isTrue(notifyStub.called);
 | 
			
		||||
      const lastCall = notifyStub.lastCall;
 | 
			
		||||
@@ -241,16 +216,11 @@ limitations under the License.
 | 
			
		||||
      assert.equal(lastCall.args[2], 'right');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('_handleCommentChange splice out', () => {
 | 
			
		||||
      const handlerSpy = sandbox.spy(element, '_handleCommentChange');
 | 
			
		||||
      const mapSpy = sandbox.spy(element, '_computeCommentMap');
 | 
			
		||||
    test('_handleCommentRangesChange splice out', () => {
 | 
			
		||||
      const notifyStub = sinon.stub();
 | 
			
		||||
      element.addListener(notifyStub);
 | 
			
		||||
 | 
			
		||||
      element.splice('comments.right', 0, 1);
 | 
			
		||||
 | 
			
		||||
      assert.isTrue(handlerSpy.called);
 | 
			
		||||
      assert.isTrue(mapSpy.called);
 | 
			
		||||
      element.splice('commentRanges', 1, 1);
 | 
			
		||||
 | 
			
		||||
      assert.isTrue(notifyStub.called);
 | 
			
		||||
      const lastCall = notifyStub.lastCall;
 | 
			
		||||
@@ -259,16 +229,12 @@ limitations under the License.
 | 
			
		||||
      assert.equal(lastCall.args[2], 'right');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('_handleCommentChange splice in', () => {
 | 
			
		||||
      const handlerSpy = sandbox.spy(element, '_handleCommentChange');
 | 
			
		||||
      const mapSpy = sandbox.spy(element, '_computeCommentMap');
 | 
			
		||||
    test('_handleCommentRangesChange splice in', () => {
 | 
			
		||||
      const notifyStub = sinon.stub();
 | 
			
		||||
      element.addListener(notifyStub);
 | 
			
		||||
 | 
			
		||||
      element.splice('comments.left', element.comments.left.length, 0, {
 | 
			
		||||
        id: '56123',
 | 
			
		||||
        line: 250,
 | 
			
		||||
        message: 'new range comment',
 | 
			
		||||
      element.splice('commentRanges', 1, 0, {
 | 
			
		||||
        side: 'left',
 | 
			
		||||
        range: {
 | 
			
		||||
          end_character: 15,
 | 
			
		||||
          end_line: 275,
 | 
			
		||||
@@ -277,9 +243,6 @@ limitations under the License.
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      assert.isTrue(handlerSpy.called);
 | 
			
		||||
      assert.isTrue(mapSpy.called);
 | 
			
		||||
 | 
			
		||||
      assert.isTrue(notifyStub.called);
 | 
			
		||||
      const lastCall = notifyStub.lastCall;
 | 
			
		||||
      assert.equal(lastCall.args[0], 250);
 | 
			
		||||
@@ -291,48 +254,48 @@ limitations under the License.
 | 
			
		||||
      // There is only one ranged comment on the left, but it spans ll.36-39.
 | 
			
		||||
      const leftKeys = [];
 | 
			
		||||
      for (let i = 36; i <= 39; i++) { leftKeys.push('' + i); }
 | 
			
		||||
      assert.deepEqual(Object.keys(element._commentMap.left).sort(),
 | 
			
		||||
      assert.deepEqual(Object.keys(element._rangesMap.left).sort(),
 | 
			
		||||
          leftKeys.sort());
 | 
			
		||||
 | 
			
		||||
      assert.equal(element._commentMap.left[36].length, 1);
 | 
			
		||||
      assert.equal(element._commentMap.left[36][0].start, 6);
 | 
			
		||||
      assert.equal(element._commentMap.left[36][0].end, -1);
 | 
			
		||||
      assert.equal(element._rangesMap.left[36].length, 1);
 | 
			
		||||
      assert.equal(element._rangesMap.left[36][0].start, 6);
 | 
			
		||||
      assert.equal(element._rangesMap.left[36][0].end, -1);
 | 
			
		||||
 | 
			
		||||
      assert.equal(element._commentMap.left[37].length, 1);
 | 
			
		||||
      assert.equal(element._commentMap.left[37][0].start, 0);
 | 
			
		||||
      assert.equal(element._commentMap.left[37][0].end, -1);
 | 
			
		||||
      assert.equal(element._rangesMap.left[37].length, 1);
 | 
			
		||||
      assert.equal(element._rangesMap.left[37][0].start, 0);
 | 
			
		||||
      assert.equal(element._rangesMap.left[37][0].end, -1);
 | 
			
		||||
 | 
			
		||||
      assert.equal(element._commentMap.left[38].length, 1);
 | 
			
		||||
      assert.equal(element._commentMap.left[38][0].start, 0);
 | 
			
		||||
      assert.equal(element._commentMap.left[38][0].end, -1);
 | 
			
		||||
      assert.equal(element._rangesMap.left[38].length, 1);
 | 
			
		||||
      assert.equal(element._rangesMap.left[38][0].start, 0);
 | 
			
		||||
      assert.equal(element._rangesMap.left[38][0].end, -1);
 | 
			
		||||
 | 
			
		||||
      assert.equal(element._commentMap.left[39].length, 1);
 | 
			
		||||
      assert.equal(element._commentMap.left[39][0].start, 0);
 | 
			
		||||
      assert.equal(element._commentMap.left[39][0].end, 9);
 | 
			
		||||
      assert.equal(element._rangesMap.left[39].length, 1);
 | 
			
		||||
      assert.equal(element._rangesMap.left[39][0].start, 0);
 | 
			
		||||
      assert.equal(element._rangesMap.left[39][0].end, 9);
 | 
			
		||||
 | 
			
		||||
      // The right has two ranged comments, one spanning ll.10-12 and the other
 | 
			
		||||
      // on line 100.
 | 
			
		||||
      const rightKeys = [];
 | 
			
		||||
      for (let i = 10; i <= 12; i++) { rightKeys.push('' + i); }
 | 
			
		||||
      rightKeys.push('55', '100');
 | 
			
		||||
      assert.deepEqual(Object.keys(element._commentMap.right).sort(),
 | 
			
		||||
      assert.deepEqual(Object.keys(element._rangesMap.right).sort(),
 | 
			
		||||
          rightKeys.sort());
 | 
			
		||||
 | 
			
		||||
      assert.equal(element._commentMap.right[10].length, 1);
 | 
			
		||||
      assert.equal(element._commentMap.right[10][0].start, 10);
 | 
			
		||||
      assert.equal(element._commentMap.right[10][0].end, -1);
 | 
			
		||||
      assert.equal(element._rangesMap.right[10].length, 1);
 | 
			
		||||
      assert.equal(element._rangesMap.right[10][0].start, 10);
 | 
			
		||||
      assert.equal(element._rangesMap.right[10][0].end, -1);
 | 
			
		||||
 | 
			
		||||
      assert.equal(element._commentMap.right[11].length, 1);
 | 
			
		||||
      assert.equal(element._commentMap.right[11][0].start, 0);
 | 
			
		||||
      assert.equal(element._commentMap.right[11][0].end, -1);
 | 
			
		||||
      assert.equal(element._rangesMap.right[11].length, 1);
 | 
			
		||||
      assert.equal(element._rangesMap.right[11][0].start, 0);
 | 
			
		||||
      assert.equal(element._rangesMap.right[11][0].end, -1);
 | 
			
		||||
 | 
			
		||||
      assert.equal(element._commentMap.right[12].length, 1);
 | 
			
		||||
      assert.equal(element._commentMap.right[12][0].start, 0);
 | 
			
		||||
      assert.equal(element._commentMap.right[12][0].end, 22);
 | 
			
		||||
      assert.equal(element._rangesMap.right[12].length, 1);
 | 
			
		||||
      assert.equal(element._rangesMap.right[12][0].start, 0);
 | 
			
		||||
      assert.equal(element._rangesMap.right[12][0].end, 22);
 | 
			
		||||
 | 
			
		||||
      assert.equal(element._commentMap.right[100].length, 1);
 | 
			
		||||
      assert.equal(element._commentMap.right[100][0].start, 5);
 | 
			
		||||
      assert.equal(element._commentMap.right[100][0].end, 15);
 | 
			
		||||
      assert.equal(element._rangesMap.right[100].length, 1);
 | 
			
		||||
      assert.equal(element._rangesMap.right[100][0].start, 5);
 | 
			
		||||
      assert.equal(element._rangesMap.right[100][0].end, 15);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('_getRangesForLine normalizes invalid ranges', () => {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user