Move gr-diff-new to gr-diff

Change-Id: Ifaad016f806c31f3df43143b3238b757faa18b20
This commit is contained in:
Andrew Bonventre
2016-03-25 16:48:13 -04:00
parent cced99ae2d
commit 2aa22125b6
24 changed files with 728 additions and 3111 deletions

View File

@@ -194,6 +194,10 @@ limitations under the License.
<td><span class="key">a</span></td>
<td>Review and publish comments</td>
</tr>
<tr>
<td><span class="key">,</span></td>
<td>Show diff preferences</td>
</tr>
</tbody>
</table>
</main>

View File

@@ -44,7 +44,6 @@ limitations under the License.
draft="[[comment.__draft]]"
show-actions="[[_showActions]]"
project-config="[[projectConfig]]"
on-height-change="_handleCommentHeightChange"
on-reply="_handleCommentReply"
on-comment-discard="_handleCommentDiscard"
on-done="_handleCommentDone"></gr-diff-comment>

View File

@@ -17,12 +17,6 @@
Polymer({
is: 'gr-diff-comment-thread',
/**
* Fired when the height of the thread changes.
*
* @event height-change
*/
/**
* Fired when the thread should be discarded.
*
@@ -44,11 +38,6 @@
},
_showActions: Boolean,
_boundWindowResizeHandler: {
type: Function,
value: function() { return this._handleWindowResize.bind(this); }
},
_lastHeight: Number,
_orderedComments: Array,
},
@@ -64,12 +53,6 @@
this._getLoggedIn().then(function(loggedIn) {
this._showActions = loggedIn;
}.bind(this));
window.addEventListener('resize', this._boundWindowResizeHandler);
},
detached: function() {
window.removeEventListener('resize', this._boundWindowResizeHandler);
},
addDraft: function(opt_lineNum) {
@@ -88,10 +71,6 @@
return this.$.restAPI.getLoggedIn();
},
_handleWindowResize: function(e) {
this._heightChanged();
},
_commentsChanged: function(changeRecord) {
this._orderedComments = this._sortedComments(this.comments);
},
@@ -133,11 +112,6 @@
}
},
_handleCommentHeightChange: function(e) {
e.stopPropagation();
this._heightChanged();
},
_handleCommentReply: function(e) {
var comment = e.detail.comment;
var quoteStr;
@@ -153,7 +127,6 @@
this.async(function() {
var commentEl = this._commentElWithDraftID(reply.__draftID);
commentEl.editing = true;
this.async(this._heightChanged.bind(this), 1);
}.bind(this), 1);
},
@@ -166,7 +139,6 @@
this.async(function() {
var commentEl = this._commentElWithDraftID(reply.__draftID);
commentEl.save();
this.async(this._heightChanged.bind(this), 1);
}.bind(this), 1);
},
@@ -215,15 +187,6 @@
this.fire('thread-discard');
return;
}
this.async(this._heightChanged.bind(this), 1);
},
_heightChanged: function() {
var height = this.$.container.offsetHeight;
if (height == this._lastHeight) { return; }
this.fire('height-change', {height: height}, {bubbles: false});
this._lastHeight = height;
},
_indexOf: function(comment, arr) {

View File

@@ -129,7 +129,6 @@ limitations under the License.
disabled="{{disabled}}"
rows="4"
bind-value="{{_editDraft}}"
on-keyup="_handleTextareaKeyup"
on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
<gr-linked-text class="message"
pre

View File

@@ -17,12 +17,6 @@
Polymer({
is: 'gr-diff-comment',
/**
* Fired when the height of the comment changes.
*
* @event height-change
*/
/**
* Fired when the Reply action is triggered.
*
@@ -75,10 +69,6 @@
this.editing = this._editDraft.length == 0;
},
attached: function() {
this._heightChanged();
},
save: function() {
this.comment.message = this._editDraft;
this.disabled = true;
@@ -101,13 +91,6 @@
}.bind(this));
},
_heightChanged: function() {
this.async(function() {
this.fire('height-change', {height: this.offsetHeight},
{bubbles: false});
}.bind(this));
},
_draftChanged: function(draft) {
this.$.container.classList.toggle('draft', draft);
},
@@ -126,7 +109,6 @@
if (this.comment && this.comment.id) {
this.$$('.cancel').hidden = !editing;
}
this._heightChanged();
},
_computeLinkToComment: function(comment) {
@@ -137,12 +119,6 @@
return draft == null || draft.trim() == '';
},
_handleTextareaKeyup: function(e) {
// TODO(andybons): This isn't always true, but I can't currently think
// of a better metric.
this._heightChanged();
},
_handleTextareaKeydown: function(e) {
if (e.keyCode == 27) { // 'esc'
this._handleCancel(e);

View File

@@ -1,97 +0,0 @@
<!--
Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
<dom-module id="gr-diff-side">
<template>
<style>
:host,
.container {
display: flex;
flex: 0 0 auto;
}
.lineNum:before,
.code:before {
/* To ensure the height is non-zero in these elements, a
zero-width space is set as its content. The character
itself doesn't matter. Just that there is something
there. */
content: '\200B';
}
.lineNum {
background-color: #eee;
color: #666;
padding: 0 .75em;
text-align: right;
}
.canComment .lineNum {
cursor: pointer;
text-decoration: underline;
}
.canComment .lineNum:hover {
background-color: #ccc;
}
.lightHighlight {
background-color: var(--light-highlight-color);
}
hl,
.darkHighlight {
background-color: var(--dark-highlight-color);
}
.br:after {
/* Line feed */
content: '\A';
}
.tab {
display: inline-block;
}
.tab.withIndicator:before {
color: #C62828;
/* >> character */
content: '\00BB';
}
.numbers,
.content {
white-space: pre;
}
.numbers .filler {
background-color: #eee;
}
.contextControl {
background-color: #fef;
}
.contextControl a:link,
.contextControl a:visited {
display: block;
text-decoration: none;
}
.numbers .contextControl {
padding: 0 .75em;
text-align: right;
}
.content .contextControl {
text-align: center;
}
</style>
<div class$="[[_computeContainerClass(canComment)]]">
<div class="numbers" id="numbers"></div>
<div class="content" id="content"></div>
</div>
</template>
<script src="gr-diff-side.js"></script>
</dom-module>

View File

@@ -1,612 +0,0 @@
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function() {
'use strict';
var CharCode = {
LESS_THAN: '<'.charCodeAt(0),
GREATER_THAN: '>'.charCodeAt(0),
AMPERSAND: '&'.charCodeAt(0),
SEMICOLON: ';'.charCodeAt(0),
};
var TAB_REGEX = /\t/g;
Polymer({
is: 'gr-diff-side',
/**
* Fired when an expand context control is clicked.
*
* @event expand-context
*/
/**
* Fired when a thread's height is changed.
*
* @event thread-height-change
*/
/**
* Fired when a draft should be added.
*
* @event add-draft
*/
/**
* Fired when a thread is removed.
*
* @event remove-thread
*/
properties: {
canComment: {
type: Boolean,
value: false,
},
content: {
type: Array,
notify: true,
observer: '_contentChanged',
},
prefs: {
type: Object,
value: function() { return {}; },
},
changeNum: String,
patchNum: String,
path: String,
projectConfig: {
type: Object,
observer: '_projectConfigChanged',
},
_lineFeedHTML: {
type: String,
value: '<span class="style-scope gr-diff-side br"></span>',
readOnly: true,
},
_highlightStartTag: {
type: String,
value: '<hl class="style-scope gr-diff-side">',
readOnly: true,
},
_highlightEndTag: {
type: String,
value: '</hl>',
readOnly: true,
},
_diffChunkLineNums: {
type: Array,
value: function() { return []; },
},
_commentThreadLineNums: {
type: Array,
value: function() { return []; },
},
_focusedLineNum: {
type: Number,
value: 1,
},
},
listeners: {
'tap': '_tapHandler',
},
observers: [
'_prefsChanged(prefs.*)',
],
rowInserted: function(index) {
this.renderLineIndexRange(index, index);
this._updateDOMIndices();
this._updateJumpIndices();
},
rowRemoved: function(index) {
var removedEls = Polymer.dom(this.root).querySelectorAll(
'[data-index="' + index + '"]');
for (var i = 0; i < removedEls.length; i++) {
removedEls[i].parentNode.removeChild(removedEls[i]);
}
this._updateDOMIndices();
this._updateJumpIndices();
},
rowUpdated: function(index) {
var removedEls = Polymer.dom(this.root).querySelectorAll(
'[data-index="' + index + '"]');
for (var i = 0; i < removedEls.length; i++) {
removedEls[i].parentNode.removeChild(removedEls[i]);
}
this.renderLineIndexRange(index, index);
},
scrollToLine: function(lineNum) {
if (isNaN(lineNum) || lineNum < 1) { return; }
var el = this.$$('.numbers .lineNum[data-line-num="' + lineNum + '"]');
if (!el) { return; }
// Calculate where the line is relative to the window.
var top = el.offsetTop;
for (var offsetParent = el.offsetParent;
offsetParent;
offsetParent = offsetParent.offsetParent) {
top += offsetParent.offsetTop;
}
// Scroll the element to the middle of the window. Dividing by a third
// instead of half the inner height feels a bit better otherwise the
// element appears to be below the center of the window even when it
// isn't.
window.scrollTo(0, top - (window.innerHeight / 3) - el.offsetHeight);
},
scrollToNextDiffChunk: function() {
this._scrollToNextChunkOrThread(this._diffChunkLineNums);
},
scrollToPreviousDiffChunk: function() {
this._scrollToPreviousChunkOrThread(this._diffChunkLineNums);
},
scrollToNextCommentThread: function() {
this._scrollToNextChunkOrThread(this._commentThreadLineNums);
},
scrollToPreviousCommentThread: function() {
this._scrollToPreviousChunkOrThread(this._commentThreadLineNums);
},
renderLineIndexRange: function(startIndex, endIndex) {
this._render(this.content, startIndex, endIndex);
},
hideElementsWithIndex: function(index) {
var els = Polymer.dom(this.root).querySelectorAll(
'[data-index="' + index + '"]');
for (var i = 0; i < els.length; i++) {
els[i].setAttribute('hidden', true);
}
},
getRowHeight: function(index) {
var row = this.content[index];
// Filler elements should not be taken into account when determining
// height calculations.
if (row.type == 'FILLER') {
return 0;
}
if (row.height != null) {
return row.height;
}
var selector = '[data-index="' + index + '"]';
var els = Polymer.dom(this.root).querySelectorAll(selector);
if (els.length != 2) {
throw Error('Rows should only consist of two elements');
}
return Math.max(els[0].offsetHeight, els[1].offsetHeight);
},
getRowNaturalHeight: function(index) {
var contentEl = this.$$('.content [data-index="' + index + '"]');
return contentEl.naturalHeight || contentEl.offsetHeight;
},
setRowNaturalHeight: function(index) {
var lineEl = this.$$('.numbers [data-index="' + index + '"]');
var contentEl = this.$$('.content [data-index="' + index + '"]');
contentEl.style.height = null;
var height = contentEl.offsetHeight;
lineEl.style.height = height + 'px';
this.content[index].height = height;
return height;
},
setRowHeight: function(index, height) {
var selector = '[data-index="' + index + '"]';
var els = Polymer.dom(this.root).querySelectorAll(selector);
for (var i = 0; i < els.length; i++) {
els[i].style.height = height + 'px';
}
this.content[index].height = height;
},
_scrollToNextChunkOrThread: function(lineNums) {
for (var i = 0; i < lineNums.length; i++) {
if (lineNums[i] > this._focusedLineNum) {
this._focusedLineNum = lineNums[i];
this.scrollToLine(this._focusedLineNum);
return;
}
}
},
_scrollToPreviousChunkOrThread: function(lineNums) {
for (var i = lineNums.length - 1; i >= 0; i--) {
if (this._focusedLineNum > lineNums[i]) {
this._focusedLineNum = lineNums[i];
this.scrollToLine(this._focusedLineNum);
return;
}
}
},
_updateJumpIndices: function() {
this._commentThreadLineNums = [];
this._diffChunkLineNums = [];
var inHighlight = false;
for (var i = 0; i < this.content.length; i++) {
switch (this.content[i].type) {
case 'COMMENT_THREAD':
this._commentThreadLineNums.push(
this.content[i].comments[0].line);
break;
case 'CODE':
// Only grab the first line of the highlighted chunk.
if (!inHighlight && this.content[i].highlight) {
this._diffChunkLineNums.push(this.content[i].lineNum);
inHighlight = true;
} else if (!this.content[i].highlight) {
inHighlight = false;
}
break;
}
}
},
_updateDOMIndices: function() {
// There is no way to select elements with a data-index greater than a
// given value. For now, just update all DOM elements.
var lineEls = Polymer.dom(this.root).querySelectorAll(
'.numbers [data-index]');
var contentEls = Polymer.dom(this.root).querySelectorAll(
'.content [data-index]');
if (lineEls.length != contentEls.length) {
throw Error(
'There must be the same number of line and content elements');
}
var index = 0;
for (var i = 0; i < this.content.length; i++) {
if (this.content[i].hidden) { continue; }
lineEls[index].setAttribute('data-index', i);
contentEls[index].setAttribute('data-index', i);
index++;
}
},
_prefsChanged: function(changeRecord) {
var prefs = changeRecord.base;
this.$.content.style.width = prefs.line_length + 'ch';
},
_projectConfigChanged: function(projectConfig) {
var threadEls =
Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
for (var i = 0; i < threadEls.length; i++) {
threadEls[i].projectConfig = projectConfig;
}
},
_contentChanged: function(diff) {
this._clearChildren(this.$.numbers);
this._clearChildren(this.$.content);
this._render(diff, 0, diff.length - 1);
this._updateJumpIndices();
},
_computeContainerClass: function(canComment) {
return 'container' + (canComment ? ' canComment' : '');
},
_tapHandler: function(e) {
var lineEl = Polymer.dom(e).rootTarget;
if (!this.canComment || !lineEl.classList.contains('lineNum')) {
return;
}
e.preventDefault();
var index = parseInt(lineEl.getAttribute('data-index'), 10);
var line = parseInt(lineEl.getAttribute('data-line-num'), 10);
this.fire('add-draft', {
index: index,
line: line
}, {bubbles: false});
},
_clearChildren: function(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
},
_handleContextControlClick: function(context, e) {
e.preventDefault();
this.fire('expand-context', {context: context}, {bubbles: false});
},
_render: function(diff, startIndex, endIndex) {
var beforeLineEl;
var beforeContentEl;
if (endIndex != diff.length - 1) {
beforeLineEl = this.$$('.numbers [data-index="' + endIndex + '"]');
beforeContentEl = this.$$('.content [data-index="' + endIndex + '"]');
if (!beforeLineEl && !beforeContentEl) {
// `endIndex` may be present within the model, but not in the DOM.
// Insert it before its successive element.
beforeLineEl = this.$$(
'.numbers [data-index="' + (endIndex + 1) + '"]');
beforeContentEl = this.$$(
'.content [data-index="' + (endIndex + 1) + '"]');
}
}
for (var i = startIndex; i <= endIndex; i++) {
if (diff[i].hidden) { continue; }
switch (diff[i].type) {
case 'CODE':
this._renderCode(diff[i], i, beforeLineEl, beforeContentEl);
break;
case 'FILLER':
this._renderFiller(diff[i], i, beforeLineEl, beforeContentEl);
break;
case 'CONTEXT_CONTROL':
this._renderContextControl(diff[i], i, beforeLineEl,
beforeContentEl);
break;
case 'COMMENT_THREAD':
this._renderCommentThread(diff[i], i, beforeLineEl,
beforeContentEl);
break;
}
}
},
_handleCommentThreadHeightChange: function(e) {
var threadEl = Polymer.dom(e).rootTarget;
var index = parseInt(threadEl.getAttribute('data-index'), 10);
this.content[index].height = e.detail.height;
var lineEl = this.$$('.numbers [data-index="' + index + '"]');
lineEl.style.height = e.detail.height + 'px';
this.fire('thread-height-change', {
index: index,
height: e.detail.height,
}, {bubbles: false});
},
_handleCommentThreadDiscard: function(e) {
var threadEl = Polymer.dom(e).rootTarget;
var index = parseInt(threadEl.getAttribute('data-index'), 10);
this.fire('remove-thread', {index: index}, {bubbles: false});
},
_renderCommentThread: function(thread, index, beforeLineEl,
beforeContentEl) {
var lineEl = this._createElement('div', 'commentThread');
lineEl.classList.add('filler');
lineEl.setAttribute('data-index', index);
var threadEl = document.createElement('gr-diff-comment-thread');
threadEl.addEventListener('height-change',
this._handleCommentThreadHeightChange.bind(this));
threadEl.addEventListener('thread-discard',
this._handleCommentThreadDiscard.bind(this));
threadEl.setAttribute('data-index', index);
threadEl.changeNum = this.changeNum;
threadEl.patchNum = thread.patchNum || this.patchNum;
threadEl.path = this.path;
threadEl.comments = thread.comments;
threadEl.projectConfig = this.projectConfig;
this.$.numbers.insertBefore(lineEl, beforeLineEl);
this.$.content.insertBefore(threadEl, beforeContentEl);
},
_renderContextControl: function(control, index, beforeLineEl,
beforeContentEl) {
var lineEl = this._createElement('div', 'contextControl');
lineEl.setAttribute('data-index', index);
lineEl.textContent = '@@';
var contentEl = this._createElement('div', 'contextControl');
contentEl.setAttribute('data-index', index);
var a = this._createElement('a');
a.href = '#';
a.textContent = 'Show ' + control.numLines + ' common ' +
(control.numLines == 1 ? 'line' : 'lines') + '...';
a.addEventListener('click',
this._handleContextControlClick.bind(this, control));
contentEl.appendChild(a);
this.$.numbers.insertBefore(lineEl, beforeLineEl);
this.$.content.insertBefore(contentEl, beforeContentEl);
},
_renderFiller: function(filler, index, beforeLineEl, beforeContentEl) {
var lineFillerEl = this._createElement('div', 'filler');
lineFillerEl.setAttribute('data-index', index);
var fillerEl = this._createElement('div', 'filler');
fillerEl.setAttribute('data-index', index);
var numLines = filler.numLines || 1;
lineFillerEl.textContent = '\n'.repeat(numLines);
for (var i = 0; i < numLines; i++) {
var newlineEl = this._createElement('span', 'br');
fillerEl.appendChild(newlineEl);
}
this.$.numbers.insertBefore(lineFillerEl, beforeLineEl);
this.$.content.insertBefore(fillerEl, beforeContentEl);
},
_renderCode: function(code, index, beforeLineEl, beforeContentEl) {
var lineNumEl = this._createElement('div', 'lineNum');
lineNumEl.setAttribute('data-line-num', code.lineNum);
lineNumEl.setAttribute('data-index', index);
var numLines = code.numLines || 1;
lineNumEl.textContent = code.lineNum + '\n'.repeat(numLines);
var contentEl = this._createElement('div', 'code');
contentEl.setAttribute('data-line-num', code.lineNum);
contentEl.setAttribute('data-index', index);
if (code.highlight) {
contentEl.classList.add(code.intraline.length > 0 ?
'lightHighlight' : 'darkHighlight');
}
var html = util.escapeHTML(code.content);
if (code.highlight && code.intraline.length > 0) {
html = this._addIntralineHighlights(code.content, html,
code.intraline);
}
if (numLines > 1) {
html = this._addNewLines(code.content, html, numLines);
}
html = this._addTabWrappers(code.content, html);
// If the html is equivalent to the text then it didn't get highlighted
// or escaped. Use textContent which is faster than innerHTML.
if (code.content == html) {
contentEl.textContent = code.content;
} else {
contentEl.innerHTML = html;
}
this.$.numbers.insertBefore(lineNumEl, beforeLineEl);
this.$.content.insertBefore(contentEl, beforeContentEl);
},
// Advance `index` by the appropriate number of characters that would
// represent one source code character and return that index. For
// example, for source code '<span>' the escaped html string is
// '&lt;span&gt;'. Advancing from index 0 on the prior html string would
// return 4, since &lt; maps to one source code character ('<').
_advanceChar: function(html, index) {
// Any tags don't count as characters
while (index < html.length &&
html.charCodeAt(index) == CharCode.LESS_THAN) {
while (index < html.length &&
html.charCodeAt(index) != CharCode.GREATER_THAN) {
index++;
}
index++; // skip the ">" itself
}
// An HTML entity (e.g., &lt;) counts as one character.
if (index < html.length &&
html.charCodeAt(index) == CharCode.AMPERSAND) {
while (index < html.length &&
html.charCodeAt(index) != CharCode.SEMICOLON) {
index++;
}
}
return index + 1;
},
_addIntralineHighlights: function(content, html, highlights) {
var startTag = this._highlightStartTag;
var endTag = this._highlightEndTag;
for (var i = 0; i < highlights.length; i++) {
var hl = highlights[i];
var htmlStartIndex = 0;
for (var j = 0; j < hl.startIndex; j++) {
htmlStartIndex = this._advanceChar(html, htmlStartIndex);
}
var htmlEndIndex = 0;
if (hl.endIndex != null) {
for (var j = 0; j < hl.endIndex; j++) {
htmlEndIndex = this._advanceChar(html, htmlEndIndex);
}
} else {
// If endIndex isn't present, continue to the end of the line.
htmlEndIndex = html.length;
}
// The start and end indices could be the same if a highlight is meant
// to start at the end of a line and continue onto the next one.
// Ignore it.
if (htmlStartIndex != htmlEndIndex) {
html = html.slice(0, htmlStartIndex) + startTag +
html.slice(htmlStartIndex, htmlEndIndex) + endTag +
html.slice(htmlEndIndex);
}
}
return html;
},
_addNewLines: function(content, html, numLines) {
var htmlIndex = 0;
var indices = [];
var numChars = 0;
for (var i = 0; i < content.length; i++) {
if (numChars > 0 && numChars % this.prefs.line_length == 0) {
indices.push(htmlIndex);
}
htmlIndex = this._advanceChar(html, htmlIndex);
if (content[i] == '\t') {
numChars += this.prefs.tab_size;
} else {
numChars++;
}
}
var result = html;
var linesLeft = numLines;
// Since the result string is being altered in place, start from the end
// of the string so that the insertion indices are not affected as the
// result string changes.
for (var i = indices.length - 1; i >= 0; i--) {
result = result.slice(0, indices[i]) + this._lineFeedHTML +
result.slice(indices[i]);
linesLeft--;
}
// numLines is the total number of lines this code block should take up.
// Fill in the remaining ones.
for (var i = 0; i < linesLeft; i++) {
result += this._lineFeedHTML;
}
return result;
},
_addTabWrappers: function(content, html) {
// TODO(andybons): CSS tab-size is not supported in IE.
// Force this to be a number to prevent arbitrary injection.
var tabSize = +this.prefs.tab_size;
var htmlStr = '<span class="style-scope gr-diff-side tab ' +
(this.prefs.show_tabs ? 'withIndicator" ' : '" ') +
'style="tab-size:' + tabSize + ';' +
'-moz-tab-size:' + tabSize + ';">\t</span>';
return html.replace(TAB_REGEX, htmlStr);
},
_createElement: function(tagName, className) {
var el = document.createElement(tagName);
// When Shady DOM is being used, these classes are added to account for
// Polymer's polyfill behavior. In order to guarantee sufficient
// specificity within the CSS rules, these are added to every element.
// Since the Polymer DOM utility functions (which would do this
// automatically) are not being used for performance reasons, this is
// done manually.
el.classList.add('style-scope', 'gr-diff-side');
if (!!className) {
el.classList.add(className);
}
return el;
},
});
})();

View File

@@ -1,300 +0,0 @@
<!DOCTYPE html>
<!--
Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-side</title>
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="gr-diff-side.html">
<test-fixture id="basic">
<template>
<gr-diff-side></gr-diff-side>
</template>
</test-fixture>
<script>
suite('gr-diff-side tests', function() {
var element;
function isVisibleInWindow(el) {
var rect = el.getBoundingClientRect();
return rect.top >= 0 && rect.left >= 0 &&
rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
}
setup(function() {
element = fixture('basic');
});
test('comments', function() {
assert.isFalse(element.$$('.container').classList.contains('canComment'));
element.canComment = true;
assert.isTrue(element.$$('.container').classList.contains('canComment'));
// TODO(andybons): Check for comment creation events firing/not firing
// when implemented.
});
test('scroll to line', function() {
var content = [];
for (var i = 0; i < 300; i++) {
content.push({
type: 'CODE',
content: 'All work and no play makes Jack a dull boy',
numLines: 1,
lineNum: i + 1,
highlight: false,
intraline: [],
});
}
element.content = content;
window.scrollTo(0, 0);
element.scrollToLine(-12849);
assert.equal(window.scrollY, 0);
element.scrollToLine('sup');
assert.equal(window.scrollY, 0);
var lineEl = element.$$('.numbers .lineNum[data-line-num="150"]');
assert.ok(lineEl);
element.scrollToLine(150);
assert.isAbove(window.scrollY, 0);
assert.isTrue(isVisibleInWindow(lineEl), 'element should be visible');
});
test('intraline highlights', function() {
var content = ' <gr-linked-text content="' +
'[[_computeCurrentRevisionMessage(change)]]"></gr-linked-text>';
var html = util.escapeHTML(content);
var highlights = [
{startIndex: 0, endIndex: 33},
{startIndex: 75},
];
assert.equal(
content.slice(highlights[0].startIndex, highlights[0].endIndex),
' <gr-linked-text content="');
assert.equal(content.slice(highlights[1].startIndex),
'"></gr-linked-text>');
var result = element._addIntralineHighlights(content, html, highlights);
var expected = element._highlightStartTag +
' &lt;gr-linked-text content=&quot;' +
element._highlightEndTag +
'[[_computeCurrentRevisionMessage(change)]]' +
element._highlightStartTag +
'&quot;&gt;&lt;&#x2F;gr-linked-text&gt;' +
element._highlightEndTag;
assert.equal(result, expected);
});
test('newlines', function() {
element.prefs = {
line_length: 80,
tab_size: 4,
};
element.content = [{
type: 'CODE',
content: 'A'.repeat(50),
numLines: 1,
lineNum: 1,
highlight: false,
intraline: [],
}];
var lineEl = element.$$('.numbers .lineNum[data-line-num="1"]');
assert.ok(lineEl);
var contentEl = element.$$('.content .code[data-line-num="1"]');
assert.ok(contentEl);
assert.equal(contentEl.innerHTML, 'A'.repeat(50));
element.content = [{
type: 'CODE',
content: 'A'.repeat(100),
numLines: 2,
lineNum: 1,
highlight: false,
intraline: [],
}];
lineEl = element.$$('.numbers .lineNum[data-line-num="1"]');
assert.ok(lineEl);
contentEl = element.$$('.content .code[data-line-num="1"]');
assert.ok(contentEl);
assert.equal(contentEl.innerHTML,
'A'.repeat(80) + element._lineFeedHTML +
'A'.repeat(20) + element._lineFeedHTML);
});
test('tabs', function(done) {
element.prefs = {
line_length: 80,
tab_size: 4,
show_tabs: true,
};
element.content = [{
type: 'CODE',
content: 'A'.repeat(50) + '\t' + 'A'.repeat(50),
numLines: 2,
lineNum: 1,
highlight: false,
intraline: [],
}];
var lineEl = element.$$('.numbers .lineNum[data-line-num="1"]');
assert.ok(lineEl);
var contentEl = element.$$('.content .code[data-line-num="1"]');
assert.ok(contentEl);
var spanEl = contentEl.childNodes[1];
assert.equal(spanEl.tagName, 'SPAN');
assert.isTrue(spanEl.classList.contains(
'style-scope', 'gr-diff-side', 'tab', 'withIndicator'));
element.prefs.show_tabs = false;
element.content = [{
type: 'CODE',
content: 'A'.repeat(50) + '\t' + 'A'.repeat(50),
numLines: 2,
lineNum: 1,
highlight: false,
intraline: [],
}];
contentEl = element.$$('.content .code[data-line-num="1"]');
assert.ok(contentEl);
spanEl = contentEl.childNodes[1];
assert.equal(spanEl.tagName, 'SPAN');
assert.isTrue(spanEl.classList.contains(
'style-scope', 'gr-diff-side', 'tab'));
var alertStub = sinon.stub(window, 'alert');
element.prefs.tab_size =
'"><img src="/" onerror="alert(1);"><span class="';
element.content = [{
type: 'CODE',
content: '\t',
numLines: 1,
lineNum: 1,
highlight: false,
intraline: [],
}];
flush(function() {
assert.isFalse(alertStub.called);
alertStub.restore();
done();
});
});
test('diff context', function() {
var content = [
{type: 'CODE', hidden: true, content: '<!DOCTYPE html>'},
{type: 'CODE', hidden: true, content: '<meta charset="utf-8">'},
{type: 'CODE', hidden: true, content: '<title>My great page</title>'},
{type: 'CODE', hidden: true, content: '<style>'},
{type: 'CODE', hidden: true, content: ' *,'},
{type: 'CODE', hidden: true, content: ' *:before,'},
{type: 'CODE', hidden: true, content: ' *:after {'},
{type: 'CODE', hidden: true, content: ' box-sizing: border-box;'},
{type: 'CONTEXT_CONTROL', numLines: 8, start: 0, end: 8},
{type: 'CODE', hidden: false, content: ' }'},
];
element.content = content;
// Only the context elements and the following code line elements should
// be present in the DOM.
var contextEls =
Polymer.dom(element.root).querySelectorAll('.contextControl');
assert.equal(contextEls.length, 2);
var codeLineEls =
Polymer.dom(element.root).querySelectorAll('.lineNum, .code');
assert.equal(codeLineEls.length, 2);
for (var i = 0; i <= 8; i++) {
element.content[i].hidden = false;
}
element.renderLineIndexRange(0, 8);
element.hideElementsWithIndex(8);
contextEls =
Polymer.dom(element.root).querySelectorAll('.contextControl');
for (var i = 0; i < contextEls.length; i++) {
assert.isTrue(contextEls[i].hasAttribute('hidden'));
}
codeLineEls =
Polymer.dom(element.root).querySelectorAll('.lineNum, .code');
// Nine lines should now be present in the DOM.
assert.equal(codeLineEls.length, 9 * 2);
});
test('tap line to add a draft', function() {
var numAddDraftEvents = 0;
sinon.stub(element, 'fire', function(eventName) {
if (eventName == 'add-draft') {
numAddDraftEvents++;
}
});
element.content = [{type: 'CODE', content: '<!DOCTYPE html>'}];
element.canComment = false;
flushAsynchronousOperations();
var lineEl = element.$$('.lineNum');
assert.ok(lineEl);
MockInteractions.tap(lineEl);
assert.equal(numAddDraftEvents, 0);
element.canComment = true;
MockInteractions.tap(lineEl);
assert.equal(numAddDraftEvents, 1);
});
test('jump to diff chunk/thread', function() {
element.content = [
{type: 'CODE', content: '', intraline: [], lineNum: 1, highlight: true},
{type: 'CODE', content: '', intraline: [], lineNum: 2, highlight: true},
{type: 'CODE', content: '', intraline: [], lineNum: 3 },
{type: 'CODE', content: '', intraline: [], lineNum: 4 },
{type: 'COMMENT_THREAD', comments: [ { line: 4 }]},
{type: 'CODE', content: '', intraline: [], lineNum: 5 },
{type: 'CODE', content: '', intraline: [], lineNum: 6, highlight: true},
{type: 'CODE', content: '', intraline: [], lineNum: 7, highlight: true},
{type: 'CODE', content: '', intraline: [], lineNum: 8 },
{type: 'COMMENT_THREAD', comments: [ { line: 8 }]},
{type: 'CODE', content: '', intraline: [], lineNum: 9 },
{type: 'CODE', content: '', intraline: [], lineNum: 10,
highlight: true},
];
var scrollToLineStub = sinon.stub(element, 'scrollToLine');
element.scrollToNextDiffChunk();
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(6));
element.scrollToPreviousDiffChunk();
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(1));
element.scrollToNextCommentThread();
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(4));
element.scrollToNextCommentThread();
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(8));
element.scrollToPreviousDiffChunk();
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(6));
scrollToLineStub.restore();
});
});
</script>

View File

@@ -162,7 +162,6 @@ limitations under the License.
</h3>
<gr-diff id="diff"
change-num="[[_changeNum]]"
prefs="{{prefs}}"
patch-range="[[_patchRange]]"
path="[[_path]]"
project-config="[[_projectConfig]]"

View File

@@ -26,10 +26,6 @@
*/
properties: {
prefs: {
type: Object,
notify: true,
},
/**
* URL params passed from the router.
*/

View File

@@ -32,7 +32,7 @@
GrDiffBuilder.TAB_REGEX = /\t/g;
GrDiffBuilder.LINE_FEED_HTML =
'<span class="style-scope gr-new-diff br"></span>';
'<span class="style-scope gr-diff br"></span>';
GrDiffBuilder.GroupType = {
ADDED: 'b',
@@ -509,7 +509,7 @@
GrDiffBuilder.prototype._addIntralineHighlights = function(content, html,
highlights) {
var START_TAG = '<hl class="style-scope gr-new-diff">';
var START_TAG = '<hl class="style-scope gr-diff">';
var END_TAG = '</hl>';
for (var i = 0; i < highlights.length; i++) {
@@ -549,7 +549,7 @@
throw Error('Invalid tab size from preferences.');
}
var str = '<span class="style-scope gr-new-diff tab ';
var str = '<span class="style-scope gr-diff tab ';
if (showTabs) {
str += 'withIndicator';
}
@@ -569,7 +569,7 @@
// Since the Polymer DOM utility functions (which would do this
// automatically) are not being used for performance reasons, this is
// done manually.
el.classList.add('style-scope', 'gr-new-diff');
el.classList.add('style-scope', 'gr-diff');
if (!!className) {
el.classList.add(className);
}

View File

@@ -20,13 +20,19 @@ limitations under the License.
<link rel="import" href="../../shared/gr-request/gr-request.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
<link rel="import" href="../gr-diff-preferences/gr-diff-preferences.html">
<link rel="import" href="../gr-diff-side/gr-diff-side.html">
<link rel="import" href="../gr-patch-range-select/gr-patch-range-select.html">
<dom-module id="gr-diff">
<template>
<style>
:host {
--light-remove-highlight-color: #fee;
--dark-remove-highlight-color: #ffd4d4;
--light-add-highlight-color: #efe;
--dark-add-highlight-color: #d4ffd4;
}
.loading {
padding: 0 var(--default-horizontal-margin) 1em;
color: #666;
@@ -45,16 +51,99 @@ limitations under the License.
display: flex;
font: 12px var(--monospace-font-family);
overflow-x: auto;
will-change: transform;
}
gr-diff-side:first-of-type {
--light-highlight-color: #fee;
--dark-highlight-color: #ffd4d4;
}
gr-diff-side:last-of-type {
--light-highlight-color: #efe;
--dark-highlight-color: #d4ffd4;
table {
border-collapse: collapse;
border-right: 1px solid #ddd;
}
.section {
background-color: #eee;
}
.blank,
.content {
background-color: #fff;
}
.lineNum,
.content {
vertical-align: top;
white-space: pre;
}
.contextLineNum:before,
.lineNum:before {
display: inline-block;
color: #666;
content: attr(data-value);
padding: 0 .75em;
text-align: right;
width: 100%;
}
.canComment .lineNum[data-value] {
cursor: pointer;
}
.canComment .lineNum[data-value]:before {
text-decoration: underline;
}
.canComment .lineNum[data-value]:hover:before {
background-color: #ccc;
}
.canComment .lineNum[data-value="FILE"]:before {
content: 'File';
}
.content {
overflow: hidden;
min-width: var(--content-width, 80ch);
}
.content.left {
-webkit-user-select: var(--left-user-select, text);
-moz-user-select: var(--left-user-select, text);
-ms-user-select: var(--left-user-select, text);
user-select: var(--left-user-select, text);
}
.content.right {
-webkit-user-select: var(--right-user-select, text);
-moz-user-select: var(--right-user-select, text);
-ms-user-select: var(--right-user-select, text);
user-select: var(--right-user-select, text);
}
.content.add hl,
.content.add.darkHighlight {
background-color: var(--dark-add-highlight-color);
}
.content.add.lightHighlight {
background-color: var(--light-add-highlight-color);
}
.content.remove hl,
.content.remove.darkHighlight {
background-color: var(--dark-remove-highlight-color);
}
.content.remove.lightHighlight {
background-color: var(--light-remove-highlight-color);
}
.contextControl {
color: #849;
background-color: #fef;
}
.contextControl gr-button {
display: block;
font-family: var(--monospace-font-family);
text-decoration: none;
}
.contextControl td:not(.lineNum) {
text-align: center;
}
.br:after {
/* Line feed */
content: '\A';
}
.tab {
display: inline-block;
}
.tab.withIndicator:before {
color: #C62828;
/* >> character */
content: '\00BB';
}
</style>
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
<div hidden$="[[_loading]]" hidden>
@@ -67,44 +156,29 @@ limitations under the License.
<gr-button link
class="prefsButton"
on-tap="_handlePrefsTap"
hidden$="[[!prefs]]"
hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]"
hidden>Diff View Preferences</gr-button>
</div>
<gr-overlay id="prefsOverlay" with-backdrop>
<gr-diff-preferences
prefs="{{prefs}}"
prefs="{{_prefs}}"
on-save="_handlePrefsSave"
on-cancel="_handlePrefsCancel"></gr-diff-preferences>
</gr-overlay>
<div class="diffContainer">
<gr-diff-side id="leftDiff"
change-num="[[changeNum]]"
patch-num="[[patchRange.basePatchNum]]"
path="[[path]]"
content="{{_diff.leftSide}}"
prefs="[[prefs]]"
can-comment="[[_loggedIn]]"
project-config="[[projectConfig]]"
on-expand-context="_handleExpandContext"
on-thread-height-change="_handleThreadHeightChange"
on-add-draft="_handleAddDraft"
on-remove-thread="_handleRemoveThread"></gr-diff-side>
<gr-diff-side id="rightDiff"
change-num="[[changeNum]]"
patch-num="[[patchRange.patchNum]]"
path="[[path]]"
content="{{_diff.rightSide}}"
prefs="[[prefs]]"
can-comment="[[_loggedIn]]"
project-config="[[projectConfig]]"
on-expand-context="_handleExpandContext"
on-thread-height-change="_handleThreadHeightChange"
on-add-draft="_handleAddDraft"
on-remove-thread="_handleRemoveThread"></gr-diff-side>
<div class$="[[_computeContainerClass(_loggedIn, _viewMode)]]"
on-tap="_handleTap"
on-mousedown="_handleMouseDown"
on-copy="_handleCopy">
<table id="diffTable"></table>
</div>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-diff-line.js"></script>
<script src="gr-diff-group.js"></script>
<script src="gr-diff-builder.js"></script>
<script src="gr-diff-builder-side-by-side.js"></script>
<script src="gr-diff-builder-unified.js"></script>
<script src="gr-diff.js"></script>
</dom-module>

File diff suppressed because it is too large Load Diff

View File

@@ -20,8 +20,6 @@ limitations under the License.
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="gr-diff.html">
@@ -35,458 +33,214 @@ limitations under the License.
<script>
suite('gr-diff tests', function() {
var element;
var server;
var getDiffStub;
var getCommentsStub;
setup(function() {
stub('gr-rest-api-interface', {
getLoggedIn: function() { return Promise.resolve(false); },
})
element = fixture('basic');
element.changeNum = 42;
element.path = 'sieve.go';
element.prefs = {
context: 10,
tab_size: 8,
};
getDiffStub = sinon.stub(element.$.restAPI, 'getDiff', function() {
return Promise.resolve({
change_type: 'MODIFIED',
content: [
{
ab: [
'<!DOCTYPE html>',
'<meta charset="utf-8">',
'<title>My great page</title>',
'<style>',
' *,',
' *:before,',
' *:after {',
' box-sizing: border-box;',
' }',
'</style>',
'<header>',
]
},
{
a: [
' Welcome ',
' to the wooorld of tomorrow!',
],
b: [
' Hello, world!',
],
},
{
ab: [
'</header>',
'<body>',
'Leela: This is the only place the ship cant hear us, so ',
'everyone pretend to shower.',
'Fry: Same as every day. Got it.',
]
},
]
});
});
getCommentsStub = sinon.stub(element.$.restAPI, 'getDiffComments',
function() {
return Promise.resolve({
baseComments: [
{
author: {
_account_id: 1000000,
name: 'Andrew Bonventre',
email: 'andybons@gmail.com',
},
id: '9af53d3f_5f2b8b82',
line: 1,
message: 'this isnt quite right',
updated: '2015-12-10 02:50:21.627000000',
}
],
comments: [
{
author: {
_account_id: 1010008,
name: 'Dave Borowitz',
email: 'dborowitz@google.com',
},
id: '001a2067_f30f3048',
line: 12,
message: 'What on earth are you thinking, here?',
updated: '2015-12-12 02:51:37.973000000',
},
{
author: {
_account_id: 1000000,
name: 'Andrew Bonventre',
email: 'andybons@gmail.com',
},
id: 'a0407443_30dfe8fb',
in_reply_to: '001a2067_f30f3048',
line: 12,
message: '¯\\_(ツ)_/¯',
updated: '2015-12-12 18:50:21.627000000',
},
],
});
}
);
server = sinon.fakeServer.create();
server.respondWith(
'PUT',
'/accounts/self/preferences.diff',
[
200,
{'Content-Type': 'application/json'},
')]}\'\n' +
JSON.stringify({context: 25}),
]
);
});
teardown(function() {
getDiffStub.restore();
getCommentsStub.restore();
server.restore();
});
test('get drafts logged out', function(done) {
element.patchRange = {basePatchNum: 0, patchNum: 0};
test('comment rendering', function(done) {
element.prefs.context = -1;
element._loggedIn = true;
element.patchRange = {
basePatchNum: 1,
patchNum: 2,
};
element.reload().then(function() {
flush(function() {
var leftThreadEls =
Polymer.dom(element.$.leftDiff.root).querySelectorAll(
'gr-diff-comment-thread');
assert.equal(leftThreadEls.length, 1);
assert.equal(leftThreadEls[0].comments.length, 1);
var rightThreadEls =
Polymer.dom(element.$.rightDiff.root).querySelectorAll(
'gr-diff-comment-thread');
assert.equal(rightThreadEls.length, 1);
assert.equal(rightThreadEls[0].comments.length, 2);
var index = leftThreadEls[0].getAttribute('data-index');
var leftFillerEls =
Polymer.dom(element.$.leftDiff.root).querySelectorAll(
'.commentThread.filler[data-index="' + index + '"]');
assert.equal(leftFillerEls.length, 1);
var rightFillerEls =
Polymer.dom(element.$.rightDiff.root).querySelectorAll(
'[data-index="' + index + '"]');
assert.equal(rightFillerEls.length, 2);
for (var i = 0; i < rightFillerEls.length; i++) {
assert.isTrue(rightFillerEls[i].classList.contains('filler'));
}
var originalHeight = rightFillerEls[0].offsetHeight;
assert.equal(rightFillerEls[1].offsetHeight, originalHeight);
assert.equal(leftThreadEls[0].offsetHeight, originalHeight);
assert.equal(leftFillerEls[0].offsetHeight, originalHeight);
// Create a comment on the opposite side of the first comment.
var rightLineEL = element.$.rightDiff.$$(
'.lineNum[data-index="' + (index - 1) + '"]');
assert.ok(rightLineEL);
MockInteractions.tap(rightLineEL);
flush(function() {
var newThreadEls =
Polymer.dom(element.$.rightDiff.root).querySelectorAll(
'[data-index="' + index + '"]');
assert.equal(newThreadEls.length, 2);
for (var i = 0; i < newThreadEls.length; i++) {
assert.isTrue(
newThreadEls[i].classList.contains('commentThread') ||
newThreadEls[i].tagName == 'GR-DIFF-COMMENT-THREAD');
}
var newHeight = newThreadEls[0].offsetHeight;
assert.equal(newThreadEls[1].offsetHeight, newHeight);
assert.equal(leftFillerEls[0].offsetHeight, newHeight);
assert.equal(leftThreadEls[0].offsetHeight, newHeight);
// The editing mode height of the right comment will be greater than
// the non-editing mode height of the left comment.
assert.isAbove(newHeight, originalHeight);
// Discard the right thread and ensure the left comment heights are
// back to their original values.
newThreadEls[1].addEventListener('thread-discard', function() {
rightFillerEls =
Polymer.dom(element.$.rightDiff.root).querySelectorAll(
'[data-index="' + index + '"]');
assert.equal(rightFillerEls.length, 2);
for (var i = 0; i < rightFillerEls.length; i++) {
assert.isTrue(rightFillerEls[i].classList.contains('filler'));
}
var originalHeight = rightFillerEls[0].offsetHeight;
assert.equal(rightFillerEls[1].offsetHeight, originalHeight);
assert.equal(leftThreadEls[0].offsetHeight, originalHeight);
assert.equal(leftFillerEls[0].offsetHeight, originalHeight);
done();
});
var commentEl = newThreadEls[1].$$('gr-diff-comment');
commentEl.fire('comment-discard');
});
});
});
server.respond();
});
test('intraline normalization', function() {
// The content and highlights are in the format returned by the Gerrit
// REST API.
var content = [
' <section class="summary">',
' <gr-linked-text content="' +
'[[_computeCurrentRevisionMessage(change)]]"></gr-linked-text>',
' </section>',
];
var highlights = [
[31, 34], [42, 26]
];
var results = element._normalizeIntralineHighlights(content, highlights);
assert.deepEqual(results, [
{
contentIndex: 0,
startIndex: 31,
},
{
contentIndex: 1,
startIndex: 0,
endIndex: 33,
},
{
contentIndex: 1,
startIndex: 75,
},
{
contentIndex: 2,
startIndex: 0,
endIndex: 6,
}
]);
content = [
' this._path = value.path;',
'',
' // When navigating away from the page, there is a possibility that the',
' // patch number is no longer a part of the URL (say when navigating to',
' // the top-level change info view) and therefore undefined in `params`.',
' if (!this._patchRange.patchNum) {',
];
highlights = [
[14, 17],
[11, 70],
[12, 67],
[12, 67],
[14, 29],
];
results = element._normalizeIntralineHighlights(content, highlights);
assert.deepEqual(results, [
{
contentIndex: 0,
startIndex: 14,
endIndex: 31,
},
{
contentIndex: 2,
startIndex: 8,
endIndex: 78,
},
{
contentIndex: 3,
startIndex: 11,
endIndex: 78,
},
{
contentIndex: 4,
startIndex: 11,
endIndex: 78,
},
{
contentIndex: 5,
startIndex: 12,
endIndex: 41,
}
]);
});
test('context', function() {
element.prefs.context = 3;
element._diffResponse = {
content: [
{
ab: [
'<!DOCTYPE html>',
'<meta charset="utf-8">',
'<title>My great page</title>',
'<style>',
' *,',
' *:before,',
' *:after {',
' box-sizing: border-box;',
' }',
'</style>',
'<header>',
]
},
{
a: [
' Welcome ',
' to the wooorld of tomorrow!',
],
b: [
' Hello, world!',
],
},
{
ab: [
'</header>',
'<body>',
'Leela: This is the only place the ship cant hear us, so ',
'everyone pretend to shower.',
'Fry: Same as every day. Got it.',
]
},
]
};
element._processContent();
// First eight lines should be hidden on both sides.
for (var i = 0; i < 8; i++) {
assert.isTrue(element._diff.leftSide[i].hidden);
assert.isTrue(element._diff.rightSide[i].hidden);
}
// A context control should be at index 8 on both sides.
var leftContext = element._diff.leftSide[8];
var rightContext = element._diff.rightSide[8];
assert.deepEqual(leftContext, rightContext);
assert.equal(leftContext.numLines, 8);
assert.equal(leftContext.start, 0);
assert.equal(leftContext.end, 8);
// Line indices 9-16 should be shown.
for (var i = 9; i <= 16; i++) {
// notOk (falsy) because the `hidden` attribute may not be present.
assert.notOk(element._diff.leftSide[i].hidden);
assert.notOk(element._diff.rightSide[i].hidden);
}
// Lines at indices 17 and 18 should be hidden.
assert.isTrue(element._diff.leftSide[17].hidden);
assert.isTrue(element._diff.rightSide[17].hidden);
assert.isTrue(element._diff.leftSide[18].hidden);
assert.isTrue(element._diff.rightSide[18].hidden);
// Context control at index 19.
leftContext = element._diff.leftSide[19];
rightContext = element._diff.rightSide[19];
assert.deepEqual(leftContext, rightContext);
assert.equal(leftContext.numLines, 2);
assert.equal(leftContext.start, 17);
assert.equal(leftContext.end, 19);
});
test('save prefs', function(done) {
element._loggedIn = false;
element.prefs = {
tab_size: 4,
context: 50,
};
element.fire('save', {}, {node: element.$$('gr-diff-preferences')});
assert.isTrue(element._diffPreferencesPromise == null);
element._loggedIn = true;
element.fire('save', {}, {node: element.$$('gr-diff-preferences')});
server.respond();
element._diffPreferencesPromise.then(function(req) {
assert.equal(req.xhr.requestBody, JSON.stringify(element.prefs));
var getDraftsStub = sinon.stub(element.$.restAPI, 'getDiffDrafts');
var loggedInStub = sinon.stub(element, '_getLoggedIn',
function() { return Promise.resolve(false); });
element._getDiffDrafts().then(function(result) {
assert.deepEqual(result, {baseComments: [], comments: []});
sinon.assert.notCalled(getDraftsStub);
loggedInStub.restore();
getDraftsStub.restore();
done();
});
});
test('visible line length', function() {
assert.equal(element._visibleLineLength('A'.repeat(5)), 5);
assert.equal(
element._visibleLineLength('A'.repeat(5) + '\t' + 'A'.repeat(5)), 18);
test('get drafts logged in', function(done) {
element.patchRange = {basePatchNum: 0, patchNum: 0};
var draftsResponse = {
baseComments: [{id: 'foo'}],
comments: [{id: 'bar'}],
};
var getDraftsStub = sinon.stub(element.$.restAPI, 'getDiffDrafts',
function() { return Promise.resolve(draftsResponse); });
var loggedInStub = sinon.stub(element, '_getLoggedIn',
function() { return Promise.resolve(true); });
element._getDiffDrafts().then(function(result) {
assert.deepEqual(result, draftsResponse);
loggedInStub.restore();
getDraftsStub.restore();
done();
});
});
test('break up common diff chunks', function() {
element._groupedBaseComments = {
1: {},
test('get comments and drafts', function(done) {
var loggedInStub = sinon.stub(element, '_getLoggedIn',
function() { return Promise.resolve(true); });
var comments = {
baseComments: [
{id: 'bc1'},
{id: 'bc2'},
],
comments: [
{id: 'c1'},
{id: 'c2'},
],
};
element._groupedComments = {
10: {},
var diffCommentsStub = sinon.stub(element, '_getDiffComments',
function() { return Promise.resolve(comments); });
var drafts = {
baseComments: [
{id: 'bd1'},
{id: 'bd2'},
],
comments: [
{id: 'd1'},
{id: 'd2'},
],
};
var ctx = {
left: {lineNum: 0},
right: {lineNum: 0},
var diffDraftsStub = sinon.stub(element, '_getDiffDrafts',
function() { return Promise.resolve(drafts); });
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: 3,
};
var content = [
{
ab: [
'Copyright (C) 2015 The Android Open Source Project',
'',
'Licensed under the Apache License, Version 2.0 (the "License");',
'you may not use this file except in compliance with the License.',
'You may obtain a copy of the License at',
'',
'http://www.apache.org/licenses/LICENSE-2.0',
'',
'Unless required by applicable law or agreed to in writing, ',
'software distributed under the License is distributed on an ',
'"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, ',
'either express or implied. See the License for the specific ',
'language governing permissions and limitations under the License.',
]
}
];
var result = element._breakUpCommonChunksWithComments(ctx, content);
assert.deepEqual(result, [
{
__noHighlight: true,
a: ['Copyright (C) 2015 The Android Open Source Project'],
b: ['Copyright (C) 2015 The Android Open Source Project'],
element.path = '/path/to/foo';
element.projectConfig = {foo: 'bar'};
element._getDiffCommentsAndDrafts().then(function(result) {
assert.deepEqual(result, {
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
left: [
{id: 'bc1'},
{id: 'bc2'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
{id: 'd2', __draft: true},
],
});
diffCommentsStub.restore();
diffDraftsStub.restore();
loggedInStub.restore();
done();
});
});
test('remove comment', function() {
element._comments = {
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
{
ab: [
'',
'Licensed under the Apache License, Version 2.0 (the "License");',
'you may not use this file except in compliance with the License.',
'You may obtain a copy of the License at',
'',
'http://www.apache.org/licenses/LICENSE-2.0',
'',
'Unless required by applicable law or agreed to in writing, ',
]
left: [
{id: 'bc1'},
{id: 'bc2'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
{id: 'd2', __draft: true},
],
};
element._removeComment({});
// Using JSON.stringify because Safari 9.1 (11601.5.17.1) doesnt seem to
// believe that one object deepEquals another even when they do :-/.
assert.equal(JSON.stringify(element._comments), JSON.stringify({
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
{
__noHighlight: true,
a: ['software distributed under the License is distributed on an '],
b: ['software distributed under the License is distributed on an ']
left: [
{id: 'bc1'},
{id: 'bc2'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
{id: 'd2', __draft: true},
],
}));
element._removeComment({id: 'bc2'});
assert.equal(JSON.stringify(element._comments), JSON.stringify({
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
{
ab: [
'"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, ',
'either express or implied. See the License for the specific ',
'language governing permissions and limitations under the License.',
]
}
]);
left: [
{id: 'bc1'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
{id: 'd2', __draft: true},
],
}));
element._removeComment({id: 'd2'});
assert.deepEqual(JSON.stringify(element._comments), JSON.stringify({
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
left: [
{id: 'bc1'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
],
}));
});
});
</script>

View File

@@ -1,184 +0,0 @@
<!--
Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-request/gr-request.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
<link rel="import" href="../gr-diff-preferences/gr-diff-preferences.html">
<link rel="import" href="../gr-patch-range-select/gr-patch-range-select.html">
<dom-module id="gr-new-diff">
<template>
<style>
:host {
--light-remove-highlight-color: #fee;
--dark-remove-highlight-color: #ffd4d4;
--light-add-highlight-color: #efe;
--dark-add-highlight-color: #d4ffd4;
}
.loading {
padding: 0 var(--default-horizontal-margin) 1em;
color: #666;
}
.header {
display: flex;
justify-content: space-between;
margin: 0 var(--default-horizontal-margin) .75em;
}
.prefsButton {
text-align: right;
}
.diffContainer {
border-bottom: 1px solid #eee;
border-top: 1px solid #eee;
display: flex;
font: 12px var(--monospace-font-family);
overflow-x: auto;
will-change: transform;
}
table {
border-collapse: collapse;
border-right: 1px solid #ddd;
}
.section {
background-color: #eee;
}
.blank,
.content {
background-color: #fff;
}
.lineNum,
.content {
vertical-align: top;
white-space: pre;
}
.contextLineNum:before,
.lineNum:before {
display: inline-block;
color: #666;
content: attr(data-value);
padding: 0 .75em;
text-align: right;
width: 100%;
}
.canComment .lineNum[data-value] {
cursor: pointer;
}
.canComment .lineNum[data-value]:before {
text-decoration: underline;
}
.canComment .lineNum[data-value]:hover:before {
background-color: #ccc;
}
.canComment .lineNum[data-value="FILE"]:before {
content: 'File';
}
.content {
overflow: hidden;
min-width: var(--content-width, 80ch);
}
.content.left {
-webkit-user-select: var(--left-user-select, text);
-moz-user-select: var(--left-user-select, text);
-ms-user-select: var(--left-user-select, text);
user-select: var(--left-user-select, text);
}
.content.right {
-webkit-user-select: var(--right-user-select, text);
-moz-user-select: var(--right-user-select, text);
-ms-user-select: var(--right-user-select, text);
user-select: var(--right-user-select, text);
}
.content.add hl,
.content.add.darkHighlight {
background-color: var(--dark-add-highlight-color);
}
.content.add.lightHighlight {
background-color: var(--light-add-highlight-color);
}
.content.remove hl,
.content.remove.darkHighlight {
background-color: var(--dark-remove-highlight-color);
}
.content.remove.lightHighlight {
background-color: var(--light-remove.highlight-color);
}
.contextControl {
color: #849;
background-color: #fef;
}
.contextControl gr-button {
display: block;
font-family: var(--monospace-font-family);
text-decoration: none;
}
.contextControl td:not(.lineNum) {
text-align: center;
}
.br:after {
/* Line feed */
content: '\A';
}
.tab {
display: inline-block;
}
.tab.withIndicator:before {
color: #C62828;
/* >> character */
content: '\00BB';
}
</style>
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
<div hidden$="[[_loading]]" hidden>
<div class="header">
<gr-patch-range-select
path="[[path]]"
change-num="[[changeNum]]"
patch-range="[[patchRange]]"
available-patches="[[availablePatches]]"></gr-patch-range-select>
<gr-button link
class="prefsButton"
on-tap="_handlePrefsTap"
hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]"
hidden>Diff View Preferences</gr-button>
</div>
<gr-overlay id="prefsOverlay" with-backdrop>
<gr-diff-preferences
prefs="{{_prefs}}"
on-save="_handlePrefsSave"
on-cancel="_handlePrefsCancel"></gr-diff-preferences>
</gr-overlay>
<div class$="[[_computeContainerClass(_loggedIn, _viewMode)]]"
on-tap="_handleTap"
on-mousedown="_handleMouseDown"
on-copy="_handleCopy">
<table id="diffTable"></table>
</div>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-diff-line.js"></script>
<script src="gr-diff-group.js"></script>
<script src="gr-diff-builder.js"></script>
<script src="gr-diff-builder-side-by-side.js"></script>
<script src="gr-diff-builder-unified.js"></script>
<script src="gr-new-diff.js"></script>
</dom-module>

View File

@@ -1,516 +0,0 @@
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function() {
'use strict';
var DiffViewMode = {
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
UNIFIED: 'UNIFIED_DIFF',
};
var DiffSide = {
LEFT: 'left',
RIGHT: 'right',
};
Polymer({
is: 'gr-new-diff',
/**
* Fired when the diff is rendered.
*
* @event render
*/
properties: {
availablePatches: Array,
changeNum: String,
patchRange: Object,
path: String,
projectConfig: {
type: Object,
observer: '_projectConfigChanged',
},
_loggedIn: {
type: Boolean,
value: false,
},
_loading: {
type: Boolean,
value: true,
},
_viewMode: {
type: String,
value: DiffViewMode.SIDE_BY_SIDE,
},
_diff: Object,
_diffBuilder: Object,
_prefs: Object,
_selectionSide: {
type: String,
observer: '_selectionSideChanged',
},
_comments: Object,
_focusedSection: {
type: Number,
value: -1,
},
_focusedThread: {
type: Number,
value: -1,
},
},
observers: [
'_prefsChanged(_prefs.*)',
],
attached: function() {
this._getLoggedIn().then(function(loggedIn) {
this._loggedIn = loggedIn;
}.bind(this));
this.addEventListener('thread-discard',
this._handleThreadDiscard.bind(this));
this.addEventListener('comment-discard',
this._handleCommentDiscard.bind(this));
},
reload: function() {
this._clearDiffContent();
this._loading = true;
var promises = [];
promises.push(this._getDiff().then(function(diff) {
this._diff = diff;
this._loading = false;
}.bind(this)));
promises.push(this._getDiffCommentsAndDrafts().then(function(comments) {
this._comments = comments;
}.bind(this)));
promises.push(this._getDiffPreferences().then(function(prefs) {
this._prefs = prefs;
}.bind(this)));
return Promise.all(promises).then(function() {
this._render();
}.bind(this));
},
scrollToLine: function(lineNum) {
if (isNaN(lineNum) || lineNum < 1) { return; }
var lineEls = Polymer.dom(this.root).querySelectorAll(
'.lineNum[data-value="' + lineNum + '"]');
// Always choose the right side.
var el = lineEls.length === 2 ? lineEls[1] : lineEls[0];
this._scrollToElement(el);
},
scrollToNextDiffChunk: function() {
this._focusedSection = this._advanceElementWithinNodeList(
this._getDeltaSections(), this._focusedSection, 1);
},
scrollToPreviousDiffChunk: function() {
this._focusedSection = this._advanceElementWithinNodeList(
this._getDeltaSections(), this._focusedSection, -1);
},
scrollToNextCommentThread: function() {
this._focusedThread = this._advanceElementWithinNodeList(
this._getCommentThreads(), this._focusedThread, 1);
},
scrollToPreviousCommentThread: function() {
this._focusedThread = this._advanceElementWithinNodeList(
this._getCommentThreads(), this._focusedThread, -1);
},
_advanceElementWithinNodeList: function(els, curIndex, direction) {
var idx = Math.max(0, Math.min(els.length - 1, curIndex + direction));
if (curIndex !== idx) {
this._scrollToElement(els[idx]);
return idx;
}
return curIndex;
},
_getCommentThreads: function() {
return Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
},
_getDeltaSections: function() {
return Polymer.dom(this.root).querySelectorAll('.section.delta');
},
_scrollToElement: function(el) {
if (!el) { return; }
// Calculate where the element is relative to the window.
var top = el.offsetTop;
for (var offsetParent = el.offsetParent;
offsetParent;
offsetParent = offsetParent.offsetParent) {
top += offsetParent.offsetTop;
}
// Scroll the element to the middle of the window. Dividing by a third
// instead of half the inner height feels a bit better otherwise the
// element appears to be below the center of the window even when it
// isn't.
window.scrollTo(0, top - (window.innerHeight / 3) +
(el.offsetHeight / 2));
},
_computeContainerClass: function(loggedIn, viewMode) {
var classes = ['diffContainer'];
switch (viewMode) {
case DiffViewMode.UNIFIED:
classes.push('unified');
break;
case DiffViewMode.SIDE_BY_SIDE:
classes.push('sideBySide');
break
default:
throw Error('Invalid view mode: ', viewMode);
}
if (loggedIn) {
classes.push('canComment');
}
return classes.join(' ');
},
_computePrefsButtonHidden: function(prefs, loggedIn) {
return !loggedIn || !prefs;
},
_handlePrefsTap: function(e) {
e.preventDefault();
this.$.prefsOverlay.open();
},
_handlePrefsSave: function(e) {
e.stopPropagation();
var el = Polymer.dom(e).rootTarget;
el.disabled = true;
this._saveDiffPreferences().then(function() {
this.$.prefsOverlay.close();
el.disabled = false;
}.bind(this)).catch(function(err) {
el.disabled = false;
alert('Oops. Something went wrong. Check the console and bug the ' +
'PolyGerrit team for assistance.');
throw err;
});
},
_saveDiffPreferences: function() {
return this.$.restAPI.saveDiffPreferences(this._prefs);
},
_handlePrefsCancel: function(e) {
e.stopPropagation();
this.$.prefsOverlay.close();
},
_handleTap: function(e) {
var el = Polymer.dom(e).rootTarget;
if (el.classList.contains('showContext')) {
this._showContext(e.detail.group, e.detail.section);
} else if (el.classList.contains('lineNum')) {
this._handleLineTap(el);
}
},
_handleLineTap: function(el) {
this._getLoggedIn().then(function(loggedIn) {
if (!loggedIn) { return; }
var value = el.getAttribute('data-value');
if (value === GrDiffLine.FILE) {
this._addDraft(el);
return;
}
var lineNum = parseInt(value, 10);
if (isNaN(lineNum)) {
throw Error('Invalid line number: ' + value);
}
this._addDraft(el, lineNum);
}.bind(this));
},
_addDraft: function(lineEl, opt_lineNum) {
var threadEl;
// Does a thread already exist at this line?
var contentEl = lineEl.nextSibling;
while (contentEl && !contentEl.classList.contains('content')) {
contentEl = contentEl.nextSibling;
}
if (contentEl.childNodes.length > 0 &&
contentEl.lastChild.nodeName === 'GR-DIFF-COMMENT-THREAD') {
threadEl = contentEl.lastChild;
} else {
var patchNum = this.patchRange.patchNum;
var side = 'REVISION';
if (contentEl.classList.contains(DiffSide.LEFT) ||
contentEl.classList.contains('remove')) {
if (this.patchRange.basePatchNum === 'PARENT') {
side = 'PARENT';
} else {
patchNum = this.patchRange.basePatchNum;
}
}
threadEl = this._builder.createCommentThread(this.changeNum, patchNum,
this.path, side, this.projectConfig);
contentEl.appendChild(threadEl);
}
threadEl.addDraft(opt_lineNum);
},
_handleThreadDiscard: function(e) {
var el = Polymer.dom(e).rootTarget;
el.parentNode.removeChild(el);
},
_handleCommentDiscard: function(e) {
var comment = Polymer.dom(e).rootTarget.comment;
this._removeComment(comment);
},
_removeComment: function(comment) {
if (!comment.id) { return; }
this._removeCommentFromSide(comment, DiffSide.LEFT) ||
this._removeCommentFromSide(comment, DiffSide.RIGHT);
},
_removeCommentFromSide: function(comment, side) {
var idx = -1;
for (var i = 0; i < this._comments[side].length; i++) {
if (this._comments[side][i].id === comment.id) {
idx = i;
break;
}
}
if (idx !== -1) {
this.splice('_comments.' + side, idx, 1);
return true;
}
return false;
},
_handleMouseDown: function(e) {
var el = Polymer.dom(e).rootTarget;
var side;
for (var node = el; node != null; node = node.parentNode) {
if (!node.classList) { continue; }
if (node.classList.contains(DiffSide.LEFT)) {
side = DiffSide.LEFT;
break;
} else if (node.classList.contains(DiffSide.RIGHT)) {
side = DiffSide.RIGHT;
break;
}
}
this._selectionSide = side;
},
_selectionSideChanged: function(side) {
if (side) {
var oppositeSide = side === DiffSide.RIGHT ?
DiffSide.LEFT : DiffSide.RIGHT;
this.customStyle['--' + side + '-user-select'] = 'text';
this.customStyle['--' + oppositeSide + '-user-select'] = 'none';
} else {
this.customStyle['--left-user-select'] = 'text';
this.customStyle['--right-user-select'] = 'text';
}
this.updateStyles();
},
_handleCopy: function(e) {
var text = this._getSelectedText(this._selectionSide);
e.clipboardData.setData('Text', text);
e.preventDefault();
},
_getSelectedText: function(opt_side) {
var sel = window.getSelection();
var range = sel.getRangeAt(0);
var doc = range.cloneContents();
var selector = '.content';
if (opt_side) {
selector += '.' + opt_side;
}
var contentEls = Polymer.dom(doc).querySelectorAll(selector);
if (contentEls.length === 0) {
return doc.textContent;
}
var text = '';
for (var i = 0; i < contentEls.length; i++) {
text += contentEls[i].textContent + '\n';
}
return text;
},
_showContext: function(group, sectionEl) {
this._builder.emitGroup(group, sectionEl);
sectionEl.parentNode.removeChild(sectionEl);
},
_prefsChanged: function(prefsChangeRecord) {
var prefs = prefsChangeRecord.base;
this.customStyle['--content-width'] = prefs.line_length + 'ch';
this.updateStyles();
if (this._diff && this._comments) {
this._render();
}
},
_render: function() {
this._clearDiffContent();
this._builder = this._getDiffBuilder(this._diff, this._comments,
this._prefs);
this._builder.emitDiff(this._diff.content);
this.async(function() {
this.fire('render', null, {bubbles: false});
}.bind(this), 1);
},
_clearDiffContent: function() {
this.$.diffTable.innerHTML = null;
},
_getDiff: function() {
return this.$.restAPI.getDiff(
this.changeNum,
this.patchRange.basePatchNum,
this.patchRange.patchNum,
this.path);
},
_getDiffComments: function() {
return this.$.restAPI.getDiffComments(
this.changeNum,
this.patchRange.basePatchNum,
this.patchRange.patchNum,
this.path);
},
_getDiffDrafts: function() {
return this._getLoggedIn().then(function(loggedIn) {
if (!loggedIn) {
return Promise.resolve({baseComments: [], comments: []});
}
return this.$.restAPI.getDiffDrafts(
this.changeNum,
this.patchRange.basePatchNum,
this.patchRange.patchNum,
this.path);
}.bind(this));
},
_getDiffCommentsAndDrafts: function() {
var promises = [];
promises.push(this._getDiffComments());
promises.push(this._getDiffDrafts());
return Promise.all(promises).then(function(results) {
return Promise.resolve({
comments: results[0],
drafts: results[1],
});
}).then(this._normalizeDiffCommentsAndDrafts.bind(this));
},
_getDiffPreferences: function() {
return this._getLoggedIn().then(function(loggedIn) {
if (!loggedIn) {
// These defaults should match the defaults in
// gerrit-extension-api/src/main/jcg/gerrit/extensions/client/DiffPreferencesInfo.java
// NOTE: There are some settings that don't apply to PolyGerrit
// (Render mode being at least one of them).
return Promise.resolve({
auto_hide_diff_table_header: true,
context: 10,
cursor_blink_rate: 0,
ignore_whitespace: 'IGNORE_NONE',
intraline_difference: true,
line_length: 100,
show_line_endings: true,
show_tabs: true,
show_whitespace_errors: true,
syntax_highlighting: true,
tab_size: 8,
theme: 'DEFAULT',
});
}
return this.$.restAPI.getDiffPreferences();
}.bind(this));
},
_normalizeDiffCommentsAndDrafts: function(results) {
function markAsDraft(d) {
d.__draft = true;
return d;
}
var baseDrafts = results.drafts.baseComments.map(markAsDraft);
var drafts = results.drafts.comments.map(markAsDraft);
return Promise.resolve({
meta: {
path: this.path,
changeNum: this.changeNum,
patchRange: this.patchRange,
projectConfig: this.projectConfig,
},
left: results.comments.baseComments.concat(baseDrafts),
right: results.comments.comments.concat(drafts),
});
},
_getLoggedIn: function() {
return this.$.restAPI.getLoggedIn();
},
_getDiffBuilder: function(diff, comments, prefs) {
if (this._viewMode === DiffViewMode.SIDE_BY_SIDE) {
return new GrDiffBuilderSideBySide(diff, comments, prefs,
this.$.diffTable);
} else if (this._viewMode === DiffViewMode.UNIFIED) {
return new GrDiffBuilderUnified(diff, comments, prefs,
this.$.diffTable);
}
throw Error('Unsupported diff view mode: ' + this._viewMode);
},
_projectConfigChanged: function(projectConfig) {
var threadEls = this._getCommentThreads();
for (var i = 0; i < threadEls.length; i++) {
threadEls[i].projectConfig = projectConfig;
}
},
});
})();

View File

@@ -1,244 +0,0 @@
<!DOCTYPE html>
<!--
Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-new-diff</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-new-diff.html">
<test-fixture id="basic">
<template>
<gr-new-diff></gr-new-diff>
</template>
</test-fixture>
<script>
suite('gr-new-diff tests', function() {
var element;
setup(function() {
stub('gr-rest-api-interface', {
getLoggedIn: function() { return Promise.resolve(false); },
})
element = fixture('basic');
});
test('get drafts logged out', function(done) {
element.patchRange = {basePatchNum: 0, patchNum: 0};
var getDraftsStub = sinon.stub(element.$.restAPI, 'getDiffDrafts');
var loggedInStub = sinon.stub(element, '_getLoggedIn',
function() { return Promise.resolve(false); });
element._getDiffDrafts().then(function(result) {
assert.deepEqual(result, {baseComments: [], comments: []});
sinon.assert.notCalled(getDraftsStub);
loggedInStub.restore();
getDraftsStub.restore();
done();
});
});
test('get drafts logged in', function(done) {
element.patchRange = {basePatchNum: 0, patchNum: 0};
var draftsResponse = {
baseComments: [{id: 'foo'}],
comments: [{id: 'bar'}],
};
var getDraftsStub = sinon.stub(element.$.restAPI, 'getDiffDrafts',
function() { return Promise.resolve(draftsResponse); });
var loggedInStub = sinon.stub(element, '_getLoggedIn',
function() { return Promise.resolve(true); });
element._getDiffDrafts().then(function(result) {
assert.deepEqual(result, draftsResponse);
loggedInStub.restore();
getDraftsStub.restore();
done();
});
});
test('get comments and drafts', function(done) {
var loggedInStub = sinon.stub(element, '_getLoggedIn',
function() { return Promise.resolve(true); });
var comments = {
baseComments: [
{id: 'bc1'},
{id: 'bc2'},
],
comments: [
{id: 'c1'},
{id: 'c2'},
],
};
var diffCommentsStub = sinon.stub(element, '_getDiffComments',
function() { return Promise.resolve(comments); });
var drafts = {
baseComments: [
{id: 'bd1'},
{id: 'bd2'},
],
comments: [
{id: 'd1'},
{id: 'd2'},
],
};
var diffDraftsStub = sinon.stub(element, '_getDiffDrafts',
function() { return Promise.resolve(drafts); });
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: 3,
};
element.path = '/path/to/foo';
element.projectConfig = {foo: 'bar'};
element._getDiffCommentsAndDrafts().then(function(result) {
assert.deepEqual(result, {
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
left: [
{id: 'bc1'},
{id: 'bc2'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
{id: 'd2', __draft: true},
],
});
diffCommentsStub.restore();
diffDraftsStub.restore();
loggedInStub.restore();
done();
});
});
test('remove comment', function() {
element._comments = {
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
left: [
{id: 'bc1'},
{id: 'bc2'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
{id: 'd2', __draft: true},
],
};
element._removeComment({});
assert.deepEqual(element._comments, {
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
left: [
{id: 'bc1'},
{id: 'bc2'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
{id: 'd2', __draft: true},
],
});
element._removeComment({id: 'bc2'});
assert.deepEqual(element._comments, {
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
left: [
{id: 'bc1'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
{id: 'd2', __draft: true},
],
});
element._removeComment({id: 'd2'});
assert.deepEqual(element._comments, {
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: 3,
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
left: [
{id: 'bc1'},
{id: 'bd1', __draft: true},
{id: 'bd2', __draft: true},
],
right: [
{id: 'c1'},
{id: 'c2'},
{id: 'd1', __draft: true},
],
});
});
});
</script>

View File

@@ -39,15 +39,13 @@ limitations under the License.
'../elements/change-list/gr-change-list-item/gr-change-list-item_test.html',
'../elements/core/gr-account-dropdown/gr-account-dropdown_test.html',
'../elements/core/gr-search-bar/gr-search-bar_test.html',
'../elements/diff/gr-diff/gr-diff-builder_test.html',
'../elements/diff/gr-diff/gr-diff-group_test.html',
'../elements/diff/gr-diff/gr-diff_test.html',
'../elements/diff/gr-diff-comment/gr-diff-comment_test.html',
'../elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html',
'../elements/diff/gr-diff-preferences/gr-diff-preferences_test.html',
'../elements/diff/gr-diff-side/gr-diff-side_test.html',
'../elements/diff/gr-diff-view/gr-diff-view_test.html',
'../elements/diff/gr-new-diff/gr-diff-builder_test.html',
'../elements/diff/gr-new-diff/gr-diff-group_test.html',
'../elements/diff/gr-new-diff/gr-new-diff_test.html',
'../elements/diff/gr-patch-range-select/gr-patch-range-select_test.html',
'../elements/shared/gr-account-label/gr-account-label_test.html',
'../elements/shared/gr-account-link/gr-account-link_test.html',