Merge changes I19a0c153,I1037c43d
* changes:
Fix filter so that it works with shadow DOM
Consolidate list-view code
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html
index 730ff74..ea0136f 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html
@@ -18,97 +18,38 @@
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-list-view/gr-list-view.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
-<link rel="import" href="../../../styles/shared-styles.html">
-<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-admin-group-list">
<template>
- <style include="shared-styles">
- :host {
- display: flex;
- flex-direction: column;
- }
- tr.group-table {
- border-bottom: 1px solid #eee;
- }
- #groupList {
- border-collapse: collapse;
- width: 100%;
- }
- #filterContainer {
- margin: 1em;
- }
- #groupFilter {
- font-size: 1em;
- max-width: 25em;
- }
- td {
- flex-shrink: 0;
- padding: .3em .5em;
- }
- th {
- background-color: #ddd;
- border-bottom: 1px solid #eee;
- font-weight: bold;
- padding: .3em .5em;
- text-align: left;
- }
- a {
- color: var(--default-text-color);
- text-decoration: none;
- }
- a:hover {
- text-decoration: underline;
- }
- nav {
- padding: .5em 0;
- text-align: center;
- }
- nav a {
- display: inline-block;
- }
- nav a:first-of-type {
- margin-right: .5em;
- }
- .description {
- width: 70%;
- }
- </style>
- <div id="filterContainer">
- <label>Filter:</label>
- <input is="iron-input"
- type="text"
- id="groupFilter"
- bind-value="{{_filter}}"
- on-input="_onValueChange">
- </div>
- <table id="groupList">
- <tr class="headerRow">
- <th class="name topHeader">Group Name</th>
- <th class="description topHeader">Group Description</th>
- <th class="visibleToAll topHeader">Visible To All</th>
- </tr>
- <template is="dom-repeat" items="[[_shownGroups]]">
- <tr class="group-table">
- <td class="name">
- <a href$="[[_getUrl(item.group_id)]]">[[item.name]]</a>
- </td>
- <td class="description">[[item.description]]</td>
- <td class="visibleToAll">[[_visibleToAll(item)]]</td>
+ <style include="shared-styles"></style>
+ <gr-list-view
+ filter="[[_filter]]"
+ items="[[_groups]]"
+ items-per-page="[[_groupsPerPage]]"
+ loading="[[_loading]]"
+ offset="[[_offset]]"
+ path="/admin/groups">
+ <table id="list">
+ <tr class="headerRow">
+ <th class="name topHeader">Group Name</th>
+ <th class="description topHeader">Group Description</th>
+ <th class="visibleToAll topHeader">Visible To All</th>
</tr>
- </template>
- </table>
- <nav>
- <a id="prevArrow"
- href$="[[_computeNavLink(_offset, -1, _groupsPerPage)]]"
- hidden$="[[_hidePrevArrow(_offset)]]" hidden>← Prev</a>
- <a id="nextArrow"
- href$="[[_computeNavLink(_offset, 1, _groupsPerPage)]]"
- hidden$="[[_hideNextArrow(_loading, _groups)]]" hidden>
- Next →</a>
- </nav>
+ <template is="dom-repeat" items="[[_shownGroups]]">
+ <tr class="table">
+ <td class="name">
+ <a href$="[[_getUrl(item.group_id)]]">[[item.name]]</a>
+ </td>
+ <td class="description">[[item.description]]</td>
+ <td class="visibleToAll">[[_visibleToAll(item)]]</td>
+ </tr>
+ </template>
+ </table>
+ </gr-list-view>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-admin-group-list.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
index 2586b0c..295aae2 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
@@ -14,8 +14,6 @@
(function() {
'use strict';
- const REQUEST_DEBOUNCE_INTERVAL_MS = 200;
-
Polymer({
is: 'gr-admin-group-list',
@@ -66,16 +64,6 @@
'previous-page': '_handlePreviousPage',
},
- _onValueChange(e) {
- this.debounce('reload', () => {
- if (e.target.value) {
- return page.show('/admin/groups/q/filter:' +
- this.encodeURL(e.target.value, false));
- }
- page.show('/admin/groups');
- }, REQUEST_DEBOUNCE_INTERVAL_MS);
- },
-
_paramsChanged(value) {
this._loading = true;
@@ -118,34 +106,8 @@
this.encodeURL(item, true);
},
- _computeNavLink(offset, direction, groupsPerPage, filter) {
- // Offset could be a string when passed from the router.
- offset = +(offset || 0);
- const newOffset = Math.max(0, offset + (groupsPerPage * direction));
- let href = this.getBaseUrl() + '/admin/groups';
- if (filter) {
- href += '/q/filter:' + filter;
- }
- if (newOffset > 0) {
- href += ',' + newOffset;
- }
- return href;
- },
-
_computeShownGroups(groups) {
return groups.slice(0, 25);
},
-
- _hidePrevArrow(offset) {
- return offset === 0;
- },
-
- _hideNextArrow(loading, groups) {
- let lastPage = false;
- if (groups.length < this._groupsPerPage + 1) {
- lastPage = true;
- }
- return loading || lastPage || !groups || !groups.length;
- },
});
})();
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
index 5bb6906..8b969f7 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
@@ -87,31 +87,6 @@
});
});
- test('test next button', done => {
- flush(() => {
- let loading;
- assert.isFalse(element._hideNextArrow(loading, groups));
- loading = true;
- assert.isTrue(element._hideNextArrow(loading, groups));
- loading = false;
- assert.isFalse(element._hideNextArrow(loading, groups));
- element._groups = [];
- assert.isTrue(element._hideNextArrow(loading, element._groups));
- groups = _.times(4, groupGenerator);
- assert.isTrue(element._hideNextArrow(loading, groups));
- done();
- });
- });
-
- test('test for prev button', () => {
- flush(() => {
- let offset = 0;
- assert.isTrue(element._hidePrevArrow(offset));
- offset = 5;
- assert.isFalse(element._hidePrevArrow(offset));
- });
- });
-
test('_shownGroups', () => {
assert.equal(element._shownGroups.length, 25);
});
@@ -130,33 +105,12 @@
element._paramsChanged(value).then(() => { flush(done); });
});
- test('test next button', done => {
- flush(() => {
- let loading;
- assert.isTrue(element._hideNextArrow(loading, groups));
- groups = _.times(1, groupGenerator);
- assert.isTrue(element._hideNextArrow(loading, groups));
- groups = _.times(26, groupGenerator);
- assert.isFalse(element._hideNextArrow(loading, groups));
- done();
- });
- });
-
test('_shownGroups', () => {
assert.equal(element._shownGroups.length, 25);
});
});
suite('filter', () => {
- test('_onValueChange', done => {
- sandbox.stub(page, 'show', url => {
- assert.equal(url, '/admin/groups/q/filter:test');
- done();
- });
- const e = {target: {value: 'test'}};
- element._onValueChange(e);
- });
-
test('_paramsChanged', done => {
sandbox.stub(element.$.restAPI, 'getGroups', () => {
return Promise.resolve(groups);
@@ -171,30 +125,6 @@
done();
});
});
-
- test('_computeNavLink', () => {
- const offset = 25;
- const groupsPerPage = 25;
- const filter = 'test';
-
- sandbox.stub(element, 'getBaseUrl', () => '');
-
- assert.equal(
- element._computeNavLink(offset, 1, groupsPerPage, filter),
- '/admin/groups/q/filter:test,50');
-
- assert.equal(
- element._computeNavLink(offset, -1, groupsPerPage, filter),
- '/admin/groups/q/filter:test');
-
- assert.equal(
- element._computeNavLink(offset, 1, groupsPerPage, null),
- '/admin/groups,50');
-
- assert.equal(
- element._computeNavLink(offset, -1, groupsPerPage, null),
- '/admin/groups');
- });
});
});
</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
index 1adc739..0d3fe90 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
@@ -18,112 +18,49 @@
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-list-view/gr-list-view.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
-<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-admin-project-list">
<template>
- <style include="shared-styles">
- :host {
- display: flex;
- flex-direction: column;
- }
- tr.project-table {
- border-bottom: 1px solid #eee;
- }
- #projectList {
- border-collapse: collapse;
- width: 100%;
- }
- #filterContainer {
- margin: 1em;
- }
- #projectFilter {
- font-size: 1em;
- max-width: 25em;
- }
- td {
- flex-shrink: 0;
- padding: .3em .5em;
- }
- th {
- background-color: #ddd;
- border-bottom: 1px solid #eee;
- font-weight: bold;
- padding: .3em .5em;
- text-align: left;
- }
- a {
- color: var(--default-text-color);
- text-decoration: none;
- }
- a:hover {
- text-decoration: underline;
- }
- nav {
- padding: .5em 0;
- text-align: center;
- }
- nav a {
- display: inline-block;
- }
- nav a:first-of-type {
- margin-right: .5em;
- }
- .description {
- width: 70%;
- }
- .loading {
- color: #666;
- padding: 1em var(--default-horizontal-margin);
- }
- </style>
- <div id="filterContainer">
- <label>Filter:</label>
- <input is="iron-input"
- type="text"
- id="projectFilter"
- bind-value="{{_filter}}"
- on-input="_onValueChange">
- </div>
- <table id="projectList">
- <tr class="headerRow">
- <th class="name topHeader">Project Name</th>
- <th class="description topHeader">Project Description</th>
- <th class="repositoryBrowser topHeader">Repository Browser</th>
- <th class="readOnly topHeader">Read only</th>
- </tr>
- <tr class="loading" hidden$="[[!_loading]]" hidden><td>Loading...</td></tr>
- <template is="dom-repeat" items="[[_shownProjects]]" hidden$="[[_loading]]" hidden>
- <tr class="project-table">
- <td class="name">
- <a href$="[[_getUrl(item.name)]]">[[item.name]]</a>
- </td>
- <td class="description">[[item.description]]</td>
- <td class="repositoryBrowser">
- <template is="dom-repeat"
- items="[[_computeWeblink(item)]]" as="link">
- <a href$="[[link.url]]" class="webLink" rel="noopener" target="_blank">
- ([[link.name]])
- </a>
- </template>
- </td>
- <td class="readOnly">[[_readOnly(item)]]</td>
+ <style include="shared-styles"></style>
+ <gr-list-view
+ filter="[[_filter]]"
+ items-per-page="[[_projectsPerPage]]"
+ items="[[_projects]]"
+ loading="[[_loading]]"
+ offset="[[_offset]]"
+ path="/admin/projects">
+ <table id="list">
+ <tr class="headerRow">
+ <th class="name topHeader">Project Name</th>
+ <th class="description topHeader">Project Description</th>
+ <th class="repositoryBrowser topHeader">Repository Browser</th>
+ <th class="readOnly topHeader">Read only</th>
</tr>
- </template>
- </table>
- <nav>
- <a id="prevArrow"
- href$="[[_computeNavLink(_offset, -1, _projectsPerPage, _filter)]]"
- hidden$="[[_hidePrevArrow(_offset)]]" hidden>← Prev</a>
- <a id="nextArrow"
- href$="[[_computeNavLink(_offset, 1, _projectsPerPage, _filter)]]"
- hidden$="[[_hideNextArrow(_loading, _projects)]]" hidden>
- Next →</a>
- </nav>
+ <tr class="loading" hidden$="[[!_loading]]" hidden><td>Loading...</td></tr>
+ <template is="dom-repeat" items="[[_shownProjects]]" hidden$="[[_loading]]" hidden>
+ <tr class="table">
+ <td class="name">
+ <a href$="[[_getUrl(item.name)]]">[[item.name]]</a>
+ </td>
+ <td class="description">[[item.description]]</td>
+ <td class="repositoryBrowser">
+ <template is="dom-repeat"
+ items="[[_computeWeblink(item)]]" as="link">
+ <a href$="[[link.url]]" class="webLink" rel="noopener" target="_blank">
+ ([[link.name]])
+ </a>
+ </template>
+ </td>
+ <td class="readOnly">[[_readOnly(item)]]</td>
+ </tr>
+ </template>
+ </table>
+ </gr-list-view>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-admin-project-list.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
index 9aacccf..e7671d5 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
@@ -14,8 +14,6 @@
(function() {
'use strict';
- const REQUEST_DEBOUNCE_INTERVAL_MS = 200;
-
Polymer({
is: 'gr-admin-project-list',
@@ -61,21 +59,6 @@
Gerrit.URLEncodingBehavior,
],
- listeners: {
- 'next-page': '_handleNextPage',
- 'previous-page': '_handlePreviousPage',
- },
-
- _onValueChange(e) {
- this.debounce('reload', () => {
- if (e.target.value) {
- return page.show('/admin/projects/q/filter:' +
- this.encodeURL(e.target.value, false));
- }
- page.show('/admin/projects');
- }, REQUEST_DEBOUNCE_INTERVAL_MS);
- },
-
_paramsChanged(value) {
this._loading = true;
@@ -119,7 +102,6 @@
this.encodeURL(item, true);
},
-
_computeWeblink(project) {
if (!project.web_links) {
return '';
@@ -128,34 +110,8 @@
return webLinks.length ? webLinks : null;
},
- _computeNavLink(offset, direction, projectsPerPage, filter) {
- // Offset could be a string when passed from the router.
- offset = +(offset || 0);
- const newOffset = Math.max(0, offset + (projectsPerPage * direction));
- let href = this.getBaseUrl() + '/admin/projects';
- if (filter) {
- href += '/q/filter:' + filter;
- }
- if (newOffset > 0) {
- href += ',' + newOffset;
- }
- return href;
- },
-
_computeShownProjects(projects) {
return projects.slice(0, 25);
},
-
- _hidePrevArrow(offset) {
- return offset === 0;
- },
-
- _hideNextArrow(loading, projects) {
- let lastPage = false;
- if (projects.length < this._projectsPerPage + 1) {
- lastPage = true;
- }
- return loading || lastPage || !projects || !projects.length;
- },
});
})();
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
index 4267055..ff8571b 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
@@ -82,31 +82,6 @@
});
});
- test('test next button', done => {
- flush(() => {
- let loading;
- assert.isFalse(element._hideNextArrow(loading, projects));
- loading = true;
- assert.isTrue(element._hideNextArrow(loading, projects));
- loading = false;
- assert.isFalse(element._hideNextArrow(loading, projects));
- element._projects = [];
- assert.isTrue(element._hideNextArrow(loading, element._projects));
- projects = _.times(4, projectGenerator);
- assert.isTrue(element._hideNextArrow(loading, projects));
- done();
- });
- });
-
- test('test for prev button', () => {
- flush(() => {
- let offset = 0;
- assert.isTrue(element._hidePrevArrow(offset));
- offset = 5;
- assert.isFalse(element._hidePrevArrow(offset));
- });
- });
-
test('_shownProjects', () => {
assert.equal(element._shownProjects.length, 25);
});
@@ -125,33 +100,12 @@
element._paramsChanged(value).then(() => { flush(done); });
});
- test('test next button', done => {
- flush(() => {
- let loading;
- assert.isTrue(element._hideNextArrow(loading, projects));
- projects = _.times(1, projectGenerator);
- assert.isTrue(element._hideNextArrow(loading, projects));
- projects = _.times(26, projectGenerator);
- assert.isFalse(element._hideNextArrow(loading, projects));
- done();
- });
- });
-
test('_shownProjects', () => {
assert.equal(element._shownProjects.length, 25);
});
});
suite('filter', () => {
- test('_onValueChange', done => {
- sandbox.stub(page, 'show', url => {
- assert.equal(url, '/admin/projects/q/filter:test');
- done();
- });
- const e = {target: {value: 'test'}};
- element._onValueChange(e);
- });
-
test('_paramsChanged', done => {
sandbox.stub(element.$.restAPI, 'getProjects', () => {
return Promise.resolve(projects);
@@ -166,30 +120,6 @@
done();
});
});
-
- test('_computeNavLink', () => {
- const offset = 25;
- const projectsPerPage = 25;
- const filter = 'test';
-
- sandbox.stub(element, 'getBaseUrl', () => '');
-
- assert.equal(
- element._computeNavLink(offset, 1, projectsPerPage, filter),
- '/admin/projects/q/filter:test,50');
-
- assert.equal(
- element._computeNavLink(offset, -1, projectsPerPage, filter),
- '/admin/projects/q/filter:test');
-
- assert.equal(
- element._computeNavLink(offset, 1, projectsPerPage, null),
- '/admin/projects,50');
-
- assert.equal(
- element._computeNavLink(offset, -1, projectsPerPage, null),
- '/admin/projects');
- });
});
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
new file mode 100644
index 0000000..0e68899
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
@@ -0,0 +1,106 @@
+<!--
+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="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-list-view">
+ <template>
+ <style include="shared-styles">
+ #filterContainer {
+ margin: 1em;
+ }
+ #filter {
+ font-size: 1em;
+ max-width: 25em;
+ }
+ a {
+ color: var(--default-text-color);
+ text-decoration: none;
+ }
+ a:hover {
+ text-decoration: underline;
+ }
+ nav {
+ padding: .5em 0;
+ text-align: center;
+ }
+ nav a {
+ display: inline-block;
+ }
+ nav a:first-of-type {
+ margin-right: .5em;
+ }
+ ::content {
+ display: flex;
+ flex-direction: column;
+ }
+ ::content tr.table {
+ border-bottom: 1px solid #eee;
+ }
+ ::content #list {
+ border-collapse: collapse;
+ width: 100%;
+ }
+ ::content td {
+ flex-shrink: 0;
+ padding: .3em .5em;
+ }
+ ::content th {
+ background-color: #ddd;
+ border-bottom: 1px solid #eee;
+ font-weight: bold;
+ padding: .3em .5em;
+ text-align: left;
+ }
+ ::content a {
+ color: var(--default-text-color);
+ text-decoration: none;
+ }
+ ::content a:hover {
+ text-decoration: underline;
+ }
+ ::content .description {
+ width: 70%;
+ }
+ ::content .loading {
+ color: #666;
+ padding: 1em var(--default-horizontal-margin);
+ }
+ </style>
+ <div id="filterContainer">
+ <label>Filter:</label>
+ <input is="iron-input"
+ type="text"
+ id="filter"
+ bind-value="{{_filter}}">
+ </div>
+ <content></content>
+ <nav>
+ <a id="prevArrow"
+ href$="[[_computeNavLink(offset, -1, itemsPerPage, filter)]]"
+ hidden$="[[_hidePrevArrow(offset)]]" hidden>← Prev</a>
+ <a id="nextArrow"
+ href$="[[_computeNavLink(offset, 1, itemsPerPage, filter)]]"
+ hidden$="[[_hideNextArrow(loading, items)]]" hidden>
+ Next →</a>
+ </nav>
+ </template>
+ <script src="gr-list-view.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
new file mode 100644
index 0000000..1188534
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
@@ -0,0 +1,80 @@
+// 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';
+
+ const REQUEST_DEBOUNCE_INTERVAL_MS = 200;
+
+ Polymer({
+ is: 'gr-list-view',
+
+ properties: {
+ items: Array,
+ itemsPerPage: Number,
+ _filter: {
+ type: String,
+ observer: '_filterChanged',
+ },
+ offset: Number,
+ loading: Boolean,
+ path: String,
+ },
+
+ behaviors: [
+ Gerrit.BaseUrlBehavior,
+ Gerrit.URLEncodingBehavior,
+ ],
+
+ listeners: {
+ 'next-page': '_handleNextPage',
+ 'previous-page': '_handlePreviousPage',
+ },
+
+ _filterChanged(filter) {
+ this.debounce('reload', () => {
+ if (filter) {
+ return page.show(`${this.path}/q/filter:` +
+ this.encodeURL(filter, false));
+ }
+ page.show(this.path);
+ }, REQUEST_DEBOUNCE_INTERVAL_MS);
+ },
+
+ _computeNavLink(offset, direction, projectsPerPage, filter) {
+ // Offset could be a string when passed from the router.
+ offset = +(offset || 0);
+ const newOffset = Math.max(0, offset + (projectsPerPage * direction));
+ let href = this.getBaseUrl() + this.path;
+ if (filter) {
+ href += '/q/filter:' + filter;
+ }
+ if (newOffset > 0) {
+ href += ',' + newOffset;
+ }
+ return href;
+ },
+
+ _hidePrevArrow(offset) {
+ return offset === 0;
+ },
+
+ _hideNextArrow(loading, projects) {
+ let lastPage = false;
+ if (projects.length < this.itemsPerPage + 1) {
+ lastPage = true;
+ }
+ return loading || lastPage || !projects || !projects.length;
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
new file mode 100644
index 0000000..10ef4e1
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
@@ -0,0 +1,112 @@
+<!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-list-view</title>
+<script src="../../../bower_components/page/page.js"></script>
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.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-list-view.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-list-view></gr-list-view>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-list-view tests', () => {
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('_computeNavLink', () => {
+ const offset = 25;
+ const projectsPerPage = 25;
+ const filter = 'test';
+ element.path = '/admin/projects';
+
+ sandbox.stub(element, 'getBaseUrl', () => '');
+
+ assert.equal(
+ element._computeNavLink(offset, 1, projectsPerPage, filter),
+ '/admin/projects/q/filter:test,50');
+
+ assert.equal(
+ element._computeNavLink(offset, -1, projectsPerPage, filter),
+ '/admin/projects/q/filter:test');
+
+ assert.equal(
+ element._computeNavLink(offset, 1, projectsPerPage, null),
+ '/admin/projects,50');
+
+ assert.equal(
+ element._computeNavLink(offset, -1, projectsPerPage, null),
+ '/admin/projects');
+ });
+
+ test('_onValueChange', done => {
+ element.path = '/admin/projects';
+ sandbox.stub(page, 'show', url => {
+ assert.equal(url, '/admin/projects/q/filter:test');
+ done();
+ });
+ const e = {target: {value: 'test'}};
+ element._onValueChange(e);
+ });
+
+ test('next button', done => {
+ element.itemsPerPage = 25;
+ projects = new Array(26);
+
+ flush(() => {
+ let loading;
+ assert.isFalse(element._hideNextArrow(loading, projects));
+ loading = true;
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ loading = false;
+ assert.isFalse(element._hideNextArrow(loading, projects));
+ element._projects = [];
+ assert.isTrue(element._hideNextArrow(loading, element._projects));
+ projects = new Array(4);
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ done();
+ });
+ });
+
+ test('prev button', () => {
+ flush(() => {
+ let offset = 0;
+ assert.isTrue(element._hidePrevArrow(offset));
+ offset = 5;
+ assert.isFalse(element._hidePrevArrow(offset));
+ });
+ });
+ });
+</script>