Merge "Set a maximum length on topic display"
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
index 4ff10cb..e1b88e8 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
@@ -23,6 +23,7 @@
 <link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
 <link rel="import" href="../../shared/gr-change-star/gr-change-star.html">
 <link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
+<link rel="import" href="../../shared/gr-limited-text/gr-limited-text.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 
 <dom-module id="gr-change-list-item">
@@ -146,7 +147,10 @@
         [[change.branch]]
       </a>
       <template is="dom-if" if="[[change.topic]]">
-        (<a href$="[[_computeTopicURL(change)]]">[[change.topic]]</a>)
+        (<a href$="[[_computeTopicURL(change)]]"><!--
+       --><gr-limited-text limit="30" text="[[change.topic]]">
+          </gr-limited-text><!--
+     --></a>)
       </template>
     </td>
     <td class="cell updated"
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 978fab2..22126fd 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -215,6 +215,7 @@
           <template is="dom-if" if="[[change.topic]]">
             <gr-linked-chip
                 text="[[change.topic]]"
+                limit="40"
                 href="[[_computeTopicURL(change.topic)]]"
                 removable="[[!_topicReadOnly]]"
                 on-remove="_handleTopicRemoved"></gr-linked-chip>
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.html b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.html
new file mode 100644
index 0000000..8f88b6a
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.html
@@ -0,0 +1,23 @@
+<!--
+Copyright (C) 2017 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="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
+
+<dom-module id="gr-limited-text">
+  <template>[[_computeDisplayText(text, limit)]]</template>
+  <script src="gr-limited-text.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
new file mode 100644
index 0000000..8d1763c
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
@@ -0,0 +1,69 @@
+// Copyright (C) 2017 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';
+
+  /*
+   * The gr-limited-text element is for displaying text with a maximum length
+   * (in number of characters) to display. If the length of the text exceeds the
+   * configured limit, then an ellipsis indicates that the text was truncated
+   * and a tooltip containing the full text is enabled.
+   */
+
+  Polymer({
+    is: 'gr-limited-text',
+
+    properties: {
+      /** The un-truncated text to display. */
+      text: String,
+
+      /** The maximum length for the text to display before truncating. */
+      limit: Number,
+
+      /** Boolean property used by Gerrit.TooltipBehavior. */
+      hasTooltip: {
+        type: Boolean,
+        value: false,
+      },
+    },
+
+    observers: [
+      '_updateTitle(text, limit)',
+    ],
+
+    behaviors: [
+      Gerrit.TooltipBehavior,
+    ],
+
+    /**
+     * The text or limit have changed. Recompute whether a tooltip needs to be
+     * enabled.
+     */
+    _updateTitle(text, limit) {
+      this.hasTooltip = text.length > limit;
+      if (this.hasTooltip) {
+        this.setAttribute('title', text);
+      } else {
+        this.removeAttribute('title');
+      }
+    },
+
+    _computeDisplayText(text, limit) {
+      if (text.length > limit) {
+        return text.substr(0, limit - 1) + '…';
+      }
+      return text;
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
new file mode 100644
index 0000000..362f8e3
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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-limited-text</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+
+<link rel="import" href="gr-limited-text.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-limited-text></gr-limited-text>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-limited-text tests', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      element = fixture('basic');
+    });
+
+    teardown(() => {
+      sandbox.restore();
+    });
+
+    test('_updateTitle', () => {
+      const updateSpy = sandbox.spy(element, '_updateTitle');
+      element.text = 'abc 123';
+      flushAsynchronousOperations();
+      assert.isFalse(updateSpy.called);
+      assert.isNotOk(element.getAttribute('title'));
+      assert.isFalse(element.hasTooltip);
+
+      element.limit = 10;
+      flushAsynchronousOperations();
+      assert.isTrue(updateSpy.calledOnce);
+      assert.isNotOk(element.getAttribute('title'));
+      assert.isFalse(element.hasTooltip);
+
+      element.limit = 3;
+      flushAsynchronousOperations();
+      assert.isTrue(updateSpy.calledTwice);
+      assert.equal(element.getAttribute('title'), 'abc 123');
+      assert.isTrue(element.hasTooltip);
+
+      element.limit = 100;
+      flushAsynchronousOperations();
+      assert.isTrue(updateSpy.calledThrice);
+      assert.isNotOk(element.getAttribute('title'));
+      assert.isFalse(element.hasTooltip);
+    });
+
+    test('_computeDisplayText', () => {
+      assert.equal(element._computeDisplayText('foo bar', 100), 'foo bar');
+      assert.equal(element._computeDisplayText('foo bar', 4), 'foo…');
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
index ef5dab8..60f664f 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
@@ -15,7 +15,9 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
 <link rel="import" href="../gr-button/gr-button.html">
+<link rel="import" href="../gr-limited-text/gr-limited-text.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 
 <dom-module id="gr-linked-chip">
@@ -61,7 +63,14 @@
       }
     </style>
     <div class$="container [[_getBackgroundClass(transparentBackground)]]">
-      <a href$="[[href]]">[[text]]</a>
+      <template is="dom-if" if="[[limit]]">
+        <a href$="[[href]]">
+          <gr-limited-text limit="[[limit]]" text="[[text]]"></gr-limited-text>
+        </a>
+      </template>
+      <template is="dom-if" if="[[!limit]]">
+        <a href$="[[href]]">[[text]]</a>
+      </template>
       <gr-button
           id="remove"
           hidden$="[[!removable]]"
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
index dca1417..bfb8dbb 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
@@ -33,6 +33,9 @@
         type: Boolean,
         value: false,
       },
+
+      /**  If provided, sets the maximum length of the content. */
+      limit: Number,
     },
 
     _getBackgroundClass(transparent) {
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 25a053a..7c8b6ff 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -137,6 +137,7 @@
     'shared/gr-js-api-interface/gr-change-actions-js-api_test.html',
     'shared/gr-js-api-interface/gr-change-reply-js-api_test.html',
     'shared/gr-js-api-interface/gr-js-api-interface_test.html',
+    'shared/gr-limited-text/gr-limited-text_test.html',
     'shared/gr-linked-chip/gr-linked-chip_test.html',
     'shared/gr-linked-text/gr-linked-text_test.html',
     'shared/gr-list-view/gr-list-view_test.html',