Merge "Make SubmoduleOp#superProjectSubscriptionsForSubmoduleBranch public"
diff --git a/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java b/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java
index 9030a1c..5fc8ba6 100644
--- a/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IntegerSubject;
import com.google.common.truth.IterableSubject;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
@@ -81,4 +82,10 @@
ContentEntry contentEntry = actual();
return Truth.assertThat(contentEntry.editB).named("intraline edits of 'b'");
}
+
+ public IntegerSubject numberOfSkippedLines() {
+ isNotNull();
+ ContentEntry contentEntry = actual();
+ return Truth.assertThat(contentEntry.skip).named("number of skipped lines");
+ }
}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index ea66374..9c31003 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -2856,7 +2856,6 @@
private void validateNewCommits(Branch.NameKey branch, ReceiveCommand cmd)
throws PermissionBackendException {
- PermissionBackend.ForRef perm = permissions.ref(branch.get());
if (!RefNames.REFS_CONFIG.equals(cmd.getRefName())
&& !(MagicBranch.isMagicBranch(cmd.getRefName())
|| NEW_PATCHSET_PATTERN.matcher(cmd.getRefName()).matches())
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index b4f7251..a3d9048 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -285,6 +285,13 @@
int aSize = a.src.size();
int bSize = b.src.size();
+ if (edits.isEmpty() && (aSize == 0 || bSize == 0)) {
+ // The diff was requested for a file which was either added or deleted but which JGit doesn't
+ // consider a file addition/deletion (e.g. requesting a diff for the old file name of a
+ // renamed file looks like a deletion).
+ return;
+ }
+
Optional<Edit> lastEdit = getLast(edits);
if (isNewlineAtEndDeleted()) {
Optional<Edit> lastLineEdit = lastEdit.filter(edit -> edit.getEndA() == aSize);
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index 53cc5ad..057f837 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -32,6 +32,9 @@
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.extensions.api.changes.FileApi;
import com.google.gerrit.extensions.api.changes.RebaseInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
+import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.common.ChangeType;
import com.google.gerrit.extensions.common.DiffInfo;
@@ -2343,6 +2346,126 @@
assertThat(changedFiles.get(FILE_NAME)).linesDeleted().isEqualTo(2);
}
+ @Test
+ public void diffOfUnmodifiedFileWithWholeFileContextReturnsFileContents() throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+ String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
+ addModifiedPatchSet(
+ changeId, FILE_NAME2, content -> content.replace("2nd line\n", "Second line\n"));
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, FILE_NAME)
+ .withBase(previousPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ // We don't list the full file contents here as that is not the focus of this test.
+ assertThat(diffInfo)
+ .content()
+ .element(0)
+ .commonLines()
+ .containsAllOf("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
+ .inOrder();
+ }
+
+ @Test
+ public void diffOfUnmodifiedFileWithCommentAndWholeFileContextReturnsFileContents()
+ throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+ String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
+ CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.comments = ImmutableMap.of(FILE_NAME, ImmutableList.of(comment));
+ gApi.changes().id(changeId).revision(previousPatchSetId).review(reviewInput);
+ addModifiedPatchSet(
+ changeId, FILE_NAME2, content -> content.replace("2nd line\n", "Second line\n"));
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, FILE_NAME)
+ .withBase(previousPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ // We don't list the full file contents here as that is not the focus of this test.
+ assertThat(diffInfo)
+ .content()
+ .element(0)
+ .commonLines()
+ .containsAllOf("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
+ .inOrder();
+ }
+
+ @Test
+ public void diffOfNonExistentFileIsAnEmptyDiffResult() throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, "a_non-existent_file.txt")
+ .withBase(initialPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ assertThat(diffInfo).content().isEmpty();
+ }
+
+ @Test
+ public void requestingDiffForOldFileNameOfRenamedFileYieldsReasonableResult() throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+ String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
+ String newFilePath = "a_new_file.txt";
+ gApi.changes().id(changeId).edit().renameFile(FILE_NAME, newFilePath);
+ gApi.changes().id(changeId).edit().publish();
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, FILE_NAME)
+ .withBase(previousPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ // This behavior has been present in Gerrit for quite some time. It differs from the results
+ // returned for other cases (e.g. requesting the diff with whole file context for an unmodified
+ // file; requesting the diff with whole file context for a non-existent file). However, it's not
+ // completely clear what should be returned. The closest would be the result of a file deletion
+ // but that might also be misleading for users as actually a file rename occurred. In fact,
+ // requesting the diff result for the old file name of a renamed file is not a reasonable use
+ // case at all. We at least guarantee that we don't run into an internal error.
+ assertThat(diffInfo).content().element(0).commonLines().isNull();
+ assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
+ }
+
+ @Test
+ public void requestingDiffForOldFileNameOfRenamedFileWithCommentOnOldFileYieldsReasonableResult()
+ throws Exception {
+ addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
+ String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
+ CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.comments = ImmutableMap.of(FILE_NAME, ImmutableList.of(comment));
+ gApi.changes().id(changeId).revision(previousPatchSetId).review(reviewInput);
+ String newFilePath = "a_new_file.txt";
+ gApi.changes().id(changeId).edit().renameFile(FILE_NAME, newFilePath);
+ gApi.changes().id(changeId).edit().publish();
+
+ DiffInfo diffInfo =
+ getDiffRequest(changeId, CURRENT, FILE_NAME)
+ .withBase(previousPatchSetId)
+ .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
+ .get();
+ // See comment for requestingDiffForOldFileNameOfRenamedFileYieldsReasonableResult().
+ // This test should additionally ensure that we also don't run into an internal error when
+ // a comment is present.
+ assertThat(diffInfo).content().element(0).commonLines().isNull();
+ assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
+ }
+
+ private static CommentInput createCommentInput(
+ int startLine, int startCharacter, int endLine, int endCharacter, String message) {
+ CommentInput comment = new CommentInput();
+ comment.range = new Comment.Range();
+ comment.range.startLine = startLine;
+ comment.range.startCharacter = startCharacter;
+ comment.range.endLine = endLine;
+ comment.range.endCharacter = endCharacter;
+ comment.message = message;
+ return comment;
+ }
+
private void assertDiffForNewFile(
PushOneCommit.Result pushResult, String path, String expectedContentSideB) throws Exception {
DiffInfo diff =
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index 5977714..3606086 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -30,6 +30,7 @@
name: 'Work in progress',
query: 'is:open owner:${user} is:wip',
selfOnly: true,
+ hideIfEmpty: true,
},
{
// Non-WIP open changes owned by viewed user. Filter out changes ignored
@@ -160,16 +161,10 @@
_getUserDashboard(user, sections, title) {
sections = sections
.filter(section => (user === 'self' || !section.selfOnly))
- .map(section => {
- const dashboardSection = {
- name: section.name,
- query: section.query.replace(USER_PLACEHOLDER_PATTERN, user),
- };
- if (section.suffixForDashboard) {
- dashboardSection.suffixForDashboard = section.suffixForDashboard;
- }
- return dashboardSection;
- });
+ .map(section => Object.assign({}, section, {
+ name: section.name,
+ query: section.query.replace(USER_PLACEHOLDER_PATTERN, user),
+ }));
return Promise.resolve({title, sections});
},
@@ -197,45 +192,57 @@
// in an async so that attachment to the DOM can take place first.
const title = params.title || this._computeTitle(user);
this.async(() => this.fire('title-change', {title}));
+ return this._reload();
+ },
+ /**
+ * Reloads the element.
+ *
+ * @return {Promise<!Object>}
+ */
+ _reload() {
this._loading = true;
-
- const dashboardPromise = params.project ?
- this._getProjectDashboard(params.project, params.dashboard) :
+ const {project, dashboard, title, user, sections} = this.params;
+ const dashboardPromise = project ?
+ this._getProjectDashboard(project, dashboard) :
this._getUserDashboard(
- params.user || 'self',
- params.sections || DEFAULT_SECTIONS,
- params.title || this._computeTitle(params.user));
+ user || 'self',
+ sections || DEFAULT_SECTIONS,
+ title || this._computeTitle(user));
- return dashboardPromise.then(dashboard => {
- if (!dashboard) {
- this._loading = false;
- return;
+ return dashboardPromise.then(this._fetchDashboardChanges.bind(this))
+ .then(() => {
+ this.$.reporting.dashboardDisplayed();
+ }).catch(err => {
+ console.warn(err);
+ }).finally(() => { this._loading = false; });
+ },
+
+ /**
+ * Fetches the changes for each dashboard section and sets this._results
+ * with the response.
+ *
+ * @param {!Object} res
+ * @return {Promise}
+ */
+ _fetchDashboardChanges(res) {
+ if (!res) { return Promise.resolve(); }
+ const queries = res.sections.map(section => {
+ if (section.suffixForDashboard) {
+ return section.query + ' ' + section.suffixForDashboard;
}
- const queries = dashboard.sections.map(section => {
- if (section.suffixForDashboard) {
- return section.query + ' ' + section.suffixForDashboard;
- }
- return section.query;
- });
- const req =
- this.$.restAPI.getChanges(null, queries, null, this.options);
- return req.then(response => {
- this._loading = false;
- this._results = response.map((results, i) => {
- return {
- sectionName: dashboard.sections[i].name,
- query: dashboard.sections[i].query,
- results,
- };
- });
- });
- }).then(() => {
- this.$.reporting.dashboardDisplayed();
- }).catch(err => {
- this._loading = false;
- console.warn(err);
+ return section.query;
});
+
+ return this.$.restAPI.getChanges(null, queries, null, this.options)
+ .then(changes => {
+ this._results = changes.map((results, i) => ({
+ sectionName: res.sections[i].name,
+ query: res.sections[i].query,
+ results,
+ })).filter((section, i) => !res.sections[i].hideIfEmpty ||
+ section.results.length);
+ });
},
_computeUserHeaderClass(userParam) {
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index a1da018..cac2627 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -207,8 +207,11 @@
sections: [
{name: 'section 1', query: 'query 1'},
{name: 'section 2', query: 'query 2 for self'},
- {name: 'section 3', query: 'self only query'},
{
+ name: 'section 3',
+ query: 'self only query',
+ selfOnly: true,
+ }, {
name: 'section 4',
query: 'query 4',
suffixForDashboard: 'suffix',
@@ -239,6 +242,21 @@
});
});
+ test('hideIfEmpty sections', () => {
+ const sections = [
+ {name: 'test1', query: 'test1', hideIfEmpty: true},
+ {name: 'test2', query: 'test2', hideIfEmpty: true},
+ ];
+ getChangesStub.restore();
+ sandbox.stub(element.$.restAPI, 'getChanges')
+ .returns(Promise.resolve([[], ['nonempty']]));
+
+ return element._fetchDashboardChanges({sections}).then(() => {
+ assert.equal(element._results.length, 1);
+ assert.equal(element._results[0].sectionName, 'test2');
+ });
+ });
+
test('_computeUserHeaderClass', () => {
assert.equal(element._computeUserHeaderClass(undefined), '');
assert.equal(element._computeUserHeaderClass(''), '');
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index a700ccd..845ffac 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -71,10 +71,15 @@
// - `detail`, optional, String: the name of the repo detail view.
// Takes any value from Gerrit.Nav.RepoDetailView.
//
+ // - Gerrit.Nav.View.DASHBOARD
+ // - `repo`, optional, String.
+ // - `sections`, optional, Array of objects with `title` and `query`
+ // strings.
+ // - `user`, optional, String.
+ //
// - Gerrit.Nav.View.ROOT:
// - no possible parameters.
-
window.Gerrit = window.Gerrit || {};
// Prevent redefinition.
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index a72feb1..3159113 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -176,6 +176,8 @@
const LEGACY_QUERY_SUFFIX_PATTERN = /,n,z$/;
+ const REPO_TOKEN_PATTERN = /\$\{(project|repo)\}/g;
+
// Polymer makes `app` intrinsically defined on the window by virtue of the
// custom element having the id "app", but it is made explicit here.
const app = document.querySelector('#app');
@@ -405,20 +407,19 @@
* @return {string}
*/
_generateDashboardUrl(params) {
+ const repoName = params.repo || params.project || null;
if (params.sections) {
// Custom dashboard.
- const queryParams = params.sections.map(section => {
- return encodeURIComponent(section.name) + '=' +
- encodeURIComponent(section.query);
- });
+ const queryParams = this._sectionsToEncodedParams(params.sections,
+ repoName);
if (params.title) {
queryParams.push('title=' + encodeURIComponent(params.title));
}
const user = params.user ? params.user : '';
return `/dashboard/${user}?${queryParams.join('&')}`;
- } else if (params.project) {
+ } else if (repoName) {
// Project dashboard.
- return `/p/${params.project}/+/dashboard/${params.dashboard}`;
+ return `/p/${repoName}/+/dashboard/${params.dashboard}`;
} else {
// User dashboard.
return `/dashboard/${params.user || 'self'}`;
@@ -426,6 +427,23 @@
},
/**
+ * @param {!Array<!{name: string, query: string}>} sections
+ * @param {string=} opt_repoName
+ * @return {!Array<string>}
+ */
+ _sectionsToEncodedParams(sections, opt_repoName) {
+ return sections.map(section => {
+ // If there is a repo name provided, make sure to substitute it into the
+ // ${repo} (or legacy ${project}) query tokens.
+ const query = opt_repoName ?
+ section.query.replace(REPO_TOKEN_PATTERN, opt_repoName) :
+ section.query;
+ return encodeURIComponent(section.name) + '=' +
+ encodeURIComponent(query);
+ });
+ },
+
+ /**
* @param {!Object} params
* @return {string}
*/
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index b68a5e9..2211039 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -375,6 +375,21 @@
'/dashboard/?section%201=query%201§ion%202=query%202');
});
+ test('custom repo dashboard', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ sections: [
+ {name: 'section 1', query: 'query 1 ${project}'},
+ {name: 'section 2', query: 'query 2 ${repo}'},
+ ],
+ repo: 'repo-name',
+ };
+ assert.equal(
+ element._generateUrl(params),
+ '/dashboard/?section%201=query%201%20repo-name&' +
+ 'section%202=query%202%20repo-name');
+ });
+
test('custom user dashboard, with title', () => {
const params = {
view: Gerrit.Nav.View.DASHBOARD,
@@ -387,7 +402,18 @@
'/dashboard/user?name=query&title=custom%20dashboard');
});
- test('project dashboard', () => {
+ test('repo dashboard', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ repo: 'gerrit/repo',
+ dashboard: 'default:main',
+ };
+ assert.equal(
+ element._generateUrl(params),
+ '/p/gerrit/repo/+/dashboard/default:main');
+ });
+
+ test('project dashboard (legacy)', () => {
const params = {
view: Gerrit.Nav.View.DASHBOARD,
project: 'gerrit/project',
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
index 22e14e9..b6959b4 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
@@ -60,15 +60,15 @@
* commentLink patterns
*/
_contentOrConfigChanged(content, config) {
- var output = Polymer.dom(this.$.output);
+ const output = Polymer.dom(this.$.output);
output.textContent = '';
- var parser = new GrLinkTextParser(config,
+ const parser = new GrLinkTextParser(config,
this._handleParseResult.bind(this), this.removeZeroWidthSpace);
parser.parse(content);
// Ensure that links originating from HTML commentlink configs open in a
// new tab. @see Issue 5567
- output.querySelectorAll('a').forEach(function(anchor) {
+ output.querySelectorAll('a').forEach(anchor => {
anchor.setAttribute('target', '_blank');
anchor.setAttribute('rel', 'noopener');
});
@@ -87,9 +87,9 @@
* @param {DocumentFragment|undefined} fragment
*/
_handleParseResult(text, href, fragment) {
- var output = Polymer.dom(this.$.output);
+ const output = Polymer.dom(this.$.output);
if (href) {
- var a = document.createElement('a');
+ const a = document.createElement('a');
a.href = href;
a.textContent = text;
a.target = '_blank';
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
index baa025e..524496a 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
@@ -37,29 +37,29 @@
</test-fixture>
<script>
- suite('gr-linked-text tests', function() {
- var element;
- var sandbox;
+ suite('gr-linked-text tests', () => {
+ let element;
+ let sandbox;
- setup(function() {
+ setup(() => {
element = fixture('basic');
sandbox = sinon.sandbox.create();
element.config = {
ph: {
match: '([Bb]ug|[Ii]ssue)\\s*#?(\\d+)',
- link: 'https://code.google.com/p/gerrit/issues/detail?id=$2'
+ link: 'https://code.google.com/p/gerrit/issues/detail?id=$2',
},
changeid: {
match: '(I[0-9a-f]{8,40})',
- link: '#/q/$1'
+ link: '#/q/$1',
},
changeid2: {
match: 'Change-Id: +(I[0-9a-f]{8,40})',
- link: '#/q/$1'
+ link: '#/q/$1',
},
googlesearch: {
match: 'google:(.+)',
- link: 'https://bing.com/search?q=$1', // html should supercede link.
+ link: 'https://bing.com/search?q=$1', // html should supercede link.
html: '<a href="https://google.com/search?q=$1">$1</a>',
},
hashedhtml: {
@@ -74,27 +74,27 @@
};
});
- teardown(function() {
+ teardown(() => {
sandbox.restore();
});
- test('URL pattern was parsed and linked.', function() {
- // Reguar inline link.
- var url = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
+ test('URL pattern was parsed and linked.', () => {
+ // Regular inline link.
+ const url = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
element.content = url;
- var linkEl = element.$.output.childNodes[0];
+ const linkEl = element.$.output.childNodes[0];
assert.equal(linkEl.target, '_blank');
assert.equal(linkEl.rel, 'noopener');
assert.equal(linkEl.href, url);
assert.equal(linkEl.textContent, url);
});
- test('Bug pattern was parsed and linked', function() {
+ test('Bug pattern was parsed and linked', () => {
// "Issue/Bug" pattern.
element.content = 'Issue 3650';
- var linkEl = element.$.output.childNodes[0];
- var url = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
+ let linkEl = element.$.output.childNodes[0];
+ const url = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
assert.equal(linkEl.target, '_blank');
assert.equal(linkEl.href, url);
assert.equal(linkEl.textContent, 'Issue 3650');
@@ -107,26 +107,26 @@
assert.equal(linkEl.textContent, 'Bug 3650');
});
- test('Change-Id pattern was parsed and linked', function() {
+ test('Change-Id pattern was parsed and linked', () => {
// "Change-Id:" pattern.
- var changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
- var prefix = 'Change-Id: ';
+ const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
+ const prefix = 'Change-Id: ';
element.content = prefix + changeID;
- var textNode = element.$.output.childNodes[0];
- var linkEl = element.$.output.childNodes[1];
+ const textNode = element.$.output.childNodes[0];
+ const linkEl = element.$.output.childNodes[1];
assert.equal(textNode.textContent, prefix);
- var url = '/q/' + changeID;
+ const url = '/q/' + changeID;
assert.equal(linkEl.target, '_blank');
// Since url is a path, the host is added automatically.
assert.isTrue(linkEl.href.endsWith(url));
assert.equal(linkEl.textContent, changeID);
});
- test('Multiple matches', function() {
+ test('Multiple matches', () => {
element.content = 'Issue 3650\nIssue 3450';
- var linkEl1 = element.$.output.childNodes[0];
- var linkEl2 = element.$.output.childNodes[2];
+ const linkEl1 = element.$.output.childNodes[0];
+ const linkEl2 = element.$.output.childNodes[2];
assert.equal(linkEl1.target, '_blank');
assert.equal(linkEl1.href,
@@ -139,22 +139,22 @@
assert.equal(linkEl2.textContent, 'Issue 3450');
});
- test('Change-Id pattern parsed before bug pattern', function() {
+ test('Change-Id pattern parsed before bug pattern', () => {
// "Change-Id:" pattern.
- var changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
- var prefix = 'Change-Id: ';
+ const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
+ const prefix = 'Change-Id: ';
// "Issue/Bug" pattern.
- var bug = 'Issue 3650';
+ const bug = 'Issue 3650';
- var changeUrl = '/q/' + changeID;
- var bugUrl = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
+ const changeUrl = '/q/' + changeID;
+ const bugUrl = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
element.content = prefix + changeID + bug;
- var textNode = element.$.output.childNodes[0];
- var changeLinkEl = element.$.output.childNodes[1];
- var bugLinkEl = element.$.output.childNodes[2];
+ const textNode = element.$.output.childNodes[0];
+ const changeLinkEl = element.$.output.childNodes[1];
+ const bugLinkEl = element.$.output.childNodes[2];
assert.equal(textNode.textContent, prefix);
@@ -167,41 +167,41 @@
assert.equal(bugLinkEl.textContent, 'Issue 3650');
});
- test('html field in link config', function() {
+ test('html field in link config', () => {
element.content = 'google:do a barrel roll';
- var linkEl = element.$.output.childNodes[0];
+ const linkEl = element.$.output.childNodes[0];
assert.equal(linkEl.getAttribute('href'),
'https://google.com/search?q=do a barrel roll');
assert.equal(linkEl.textContent, 'do a barrel roll');
});
- test('removing hash from links', function() {
+ test('removing hash from links', () => {
element.content = 'hash:foo';
- var linkEl = element.$.output.childNodes[0];
+ const linkEl = element.$.output.childNodes[0];
assert.isTrue(linkEl.href.endsWith('/awesomesauce'));
assert.equal(linkEl.textContent, 'foo');
});
- test('disabled config', function() {
+ test('disabled config', () => {
element.content = 'foo:baz';
assert.equal(element.$.output.innerHTML, 'foo:baz');
});
- test('R=email labels link correctly', function() {
+ test('R=email labels link correctly', () => {
element.removeZeroWidthSpace = true;
element.content = 'R=\u200Btest@google.com';
assert.equal(element.$.output.textContent, 'R=test@google.com');
assert.equal(element.$.output.innerHTML.match(/(R=<a)/g).length, 1);
});
- test('CC=email labels link correctly', function() {
+ test('CC=email labels link correctly', () => {
element.removeZeroWidthSpace = true;
element.content = 'CC=\u200Btest@google.com';
assert.equal(element.$.output.textContent, 'CC=test@google.com');
assert.equal(element.$.output.innerHTML.match(/(CC=<a)/g).length, 1);
});
- test('only {http,https,mailto} protocols are linkified', function() {
+ test('only {http,https,mailto} protocols are linkified', () => {
element.content = 'xx mailto:test@google.com yy';
let links = element.$.output.querySelectorAll('a');
assert.equal(links.length, 1);
@@ -226,7 +226,7 @@
assert.equal(links.length, 0);
});
- test('overlapping links', function() {
+ test('overlapping links', () => {
element.config = {
b1: {
match: '(B:\\s*)(\\d+)',
@@ -238,7 +238,7 @@
},
};
element.content = '- B: 123, 45';
- var links = Polymer.dom(element.root).querySelectorAll('a');
+ const links = Polymer.dom(element.root).querySelectorAll('a');
assert.equal(links.length, 2);
assert.equal(element.$$('span').textContent, '- B: 123, 45');
@@ -250,31 +250,31 @@
assert.equal(links[1].textContent, '45');
});
- test('_contentOrConfigChanged called with config', function() {
- var contentStub = sandbox.stub(element, '_contentChanged');
- var contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
+ test('_contentOrConfigChanged called with config', () => {
+ const contentStub = sandbox.stub(element, '_contentChanged');
+ const contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
element.content = 'some text';
assert.isTrue(contentStub.called);
assert.isTrue(contentConfigStub.called);
});
});
- suite('gr-linked-text with null config', function() {
- var element;
- var sandbox;
+ suite('gr-linked-text with null config', () => {
+ let element;
+ let sandbox;
- setup(function() {
+ setup(() => {
element = fixture('basic');
sandbox = sinon.sandbox.create();
});
- teardown(function() {
+ teardown(() => {
sandbox.restore();
});
- test('_contentOrConfigChanged not called without config', function() {
- var contentStub = sandbox.stub(element, '_contentChanged');
- var contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
+ test('_contentOrConfigChanged not called without config', () => {
+ const contentStub = sandbox.stub(element, '_contentChanged');
+ const contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
element.content = 'some text';
assert.isTrue(contentStub.called);
assert.isFalse(contentConfigStub.called);
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index 8b49ca0..8526c3e 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -41,8 +41,8 @@
* @param {Object|null|undefined} linkConfig Comment links as specified by the
* commentlinks field on a project config.
* @param {Function} callback The callback to be fired when an intermediate
- * parse result is emitted. The callback is passed text and href strings if
- * a link is to be created, or a document fragment otherwise.
+ * parse result is emitted. The callback is passed text and href strings
+ * if a link is to be created, or a document fragment otherwise.
* @param {boolean|undefined} opt_removeZeroWidthSpace If true, zero-width
* spaces will be removed from R=<email> and CC=<email> expressions.
*/
@@ -73,14 +73,14 @@
*/
GrLinkTextParser.prototype.processLinks = function(text, outputArray) {
this.sortArrayReverse(outputArray);
- var fragment = document.createDocumentFragment();
- var cursor = text.length;
+ const fragment = document.createDocumentFragment();
+ let cursor = text.length;
// Start inserting linkified URLs from the end of the String. That way, the
// string positions of the items don't change as we iterate through.
- outputArray.forEach(function(item) {
- // Add any text between the current linkified item and the item added before
- // if it exists.
+ outputArray.forEach(item => {
+ // Add any text between the current linkified item and the item added
+ // before if it exists.
if (item.position + item.length !== cursor) {
fragment.insertBefore(
document.createTextNode(
@@ -130,32 +130,32 @@
*/
GrLinkTextParser.prototype.addItem =
function(text, href, html, position, length, outputArray) {
- var htmlOutput = '';
+ let htmlOutput = '';
- if (href) {
- var a = document.createElement('a');
- a.href = href;
- a.textContent = text;
- a.target = '_blank';
- a.rel = 'noopener';
- htmlOutput = a;
- } else if (html) {
- var fragment = document.createDocumentFragment();
+ if (href) {
+ const a = document.createElement('a');
+ a.href = href;
+ a.textContent = text;
+ a.target = '_blank';
+ a.rel = 'noopener';
+ htmlOutput = a;
+ } else if (html) {
+ const fragment = document.createDocumentFragment();
// Create temporary div to hold the nodes in.
- var div = document.createElement('div');
- div.innerHTML = html;
- while (div.firstChild) {
- fragment.appendChild(div.firstChild);
- }
- htmlOutput = fragment;
- }
+ const div = document.createElement('div');
+ div.innerHTML = html;
+ while (div.firstChild) {
+ fragment.appendChild(div.firstChild);
+ }
+ htmlOutput = fragment;
+ }
- outputArray.push({
- html: htmlOutput,
- position: position,
- length: length,
- });
- };
+ outputArray.push({
+ html: htmlOutput,
+ position,
+ length,
+ });
+ };
/**
* Create a CommentLinkItem for a link and append it to the given output
@@ -171,9 +171,9 @@
*/
GrLinkTextParser.prototype.addLink =
function(text, href, position, length, outputArray) {
- if (!text || this.hasOverlap(position, length, outputArray)) { return; }
- this.addItem(text, href, null, position, length, outputArray);
- };
+ if (!text || this.hasOverlap(position, length, outputArray)) { return; }
+ this.addItem(text, href, null, position, length, outputArray);
+ };
/**
* Create a CommentLinkItem specified by an HTMl string and append it to the
@@ -188,9 +188,9 @@
*/
GrLinkTextParser.prototype.addHTML =
function(html, position, length, outputArray) {
- if (this.hasOverlap(position, length, outputArray)) { return; }
- this.addItem(null, null, html, position, length, outputArray);
- };
+ if (this.hasOverlap(position, length, outputArray)) { return; }
+ this.addItem(null, null, html, position, length, outputArray);
+ };
/**
* Does the given range overlap with anything already in the item list.
@@ -200,18 +200,18 @@
*/
GrLinkTextParser.prototype.hasOverlap =
function(position, length, outputArray) {
- var endPosition = position + length;
- for (var i = 0; i < outputArray.length; i++) {
- var arrayItemStart = outputArray[i].position;
- var arrayItemEnd = outputArray[i].position + outputArray[i].length;
- if ((position >= arrayItemStart && position < arrayItemEnd) ||
+ const endPosition = position + length;
+ for (let i = 0; i < outputArray.length; i++) {
+ const arrayItemStart = outputArray[i].position;
+ const arrayItemEnd = outputArray[i].position + outputArray[i].length;
+ if ((position >= arrayItemStart && position < arrayItemEnd) ||
(endPosition > arrayItemStart && endPosition <= arrayItemEnd) ||
(position === arrayItemStart && position === arrayItemEnd)) {
return true;
- }
- }
- return false;
- };
+ }
+ }
+ return false;
+ };
/**
* Parse the given source text and emit callbacks for the items that are
@@ -241,9 +241,9 @@
text = text.replace(/^(CC|R)=\u200B/gm, '$1=');
}
- // If the href is provided then ba-linkify has recognized it as a URL. If the
- // source text does not include a protocol, the protocol will be added by
- // ba-linkify. Create the link if the href is provided and its protocol
+ // If the href is provided then ba-linkify has recognized it as a URL. If
+ // the source text does not include a protocol, the protocol will be added
+ // by ba-linkify. Create the link if the href is provided and its protocol
// matches the expected pattern.
if (href && URL_PROTOCOL_PATTERN.test(href)) {
this.addText(text, href);
@@ -262,9 +262,10 @@
* object.
*/
GrLinkTextParser.prototype.parseLinks = function(text, patterns) {
- // The outputArray is used to store all of the matches found for all patterns.
- var outputArray = [];
- for (var p in patterns) {
+ // The outputArray is used to store all of the matches found for all
+ // patterns.
+ const outputArray = [];
+ for (const p in patterns) {
if (patterns[p].enabled != null && patterns[p].enabled == false) {
continue;
}
@@ -279,38 +280,37 @@
}
}
- var pattern = new RegExp(patterns[p].match, 'g');
+ const pattern = new RegExp(patterns[p].match, 'g');
- var match;
- var textToCheck = text;
- var susbtrIndex = 0;
+ let match;
+ let textToCheck = text;
+ let susbtrIndex = 0;
while ((match = pattern.exec(textToCheck)) != null) {
textToCheck = textToCheck.substr(match.index + match[0].length);
- var result = match[0].replace(pattern,
+ let result = match[0].replace(pattern,
patterns[p].html || patterns[p].link);
+ let i;
// Skip portion of replacement string that is equal to original.
- for (var i = 0; i < result.length; i++) {
- if (result[i] !== match[0][i]) {
- break;
- }
+ for (i = 0; i < result.length; i++) {
+ if (result[i] !== match[0][i]) { break; }
}
result = result.slice(i);
if (patterns[p].html) {
this.addHTML(
- result,
- susbtrIndex + match.index + i,
- match[0].length - i,
- outputArray);
+ result,
+ susbtrIndex + match.index + i,
+ match[0].length - i,
+ outputArray);
} else if (patterns[p].link) {
this.addLink(
- match[0],
- result,
- susbtrIndex + match.index + i,
- match[0].length - i,
- outputArray);
+ match[0],
+ result,
+ susbtrIndex + match.index + i,
+ match[0].length - i,
+ outputArray);
} else {
throw Error('linkconfig entry ' + p +
' doesn’t contain a link or html attribute.');
diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index 9590f15..f401735 100644
--- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -38,7 +38,7 @@
{if $deprecateGwtUi}window.DEPRECATE_GWT_UI = true;{/if}
{if $versionInfo}window.VERSION_INFO = '{$versionInfo}';{/if}
{if $staticResourcePath != ''}window.STATIC_RESOURCE_PATH = '{$staticResourcePath}';{/if}
- {if $assetsPath != ''}window.ASSETS_PATH = '{$assetsPath}';{/if}
+ {if $assetsPath}window.ASSETS_PATH = '{$assetsPath}';{/if}
</script>{\n}
{if $faviconPath}