PolyGerrit: Implement support for New Agreement screen

Bug: Issue 6866
Bug: Issue 6783
Change-Id: I3e69d317255f7318e225ff54cf192d9812fe0a70
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 af2b9e6..43bf0ff 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -21,7 +21,8 @@
     CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
     PROJECT_DASHBOARD: /^\/p\/(.+)\/\+\/dashboard\/(.+)/,
 
-    AGREEMENTS: /^\/settings\/(agreements|new-agreement)/,
+    AGREEMENTS: /^\/settings\/agreements\/?/,
+    NEW_AGREEMENTS: /^\/settings\/new-agreement\/?/,
     REGISTER: /^\/register(\/.*)?$/,
 
     // Pattern for login and logout URLs intended to be passed-through. May
@@ -771,6 +772,9 @@
 
       this._mapRoute(RoutePattern.AGREEMENTS, '_handleAgreementsRoute', true);
 
+      this._mapRoute(RoutePattern.NEW_AGREEMENTS, '_handleNewAgreementsRoute',
+          true);
+
       this._mapRoute(RoutePattern.SETTINGS_LEGACY,
           '_handleSettingsLegacyRoute', true);
 
@@ -1272,7 +1276,13 @@
       }
     },
 
+    // TODO fix this so it properly redirects
+    // to /settings#Agreements (Scrolls down)
     _handleAgreementsRoute(data) {
+      this._redirect('/settings/#Agreements');
+    },
+
+    _handleNewAgreementsRoute(data) {
       data.params.view = Gerrit.Nav.View.AGREEMENTS;
       this._setParams(data.params);
     },
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 ae002af..1b35443 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
@@ -134,6 +134,7 @@
         '_handleGroupListOffsetRoute',
         '_handleGroupMembersRoute',
         '_handleGroupRoute',
+        '_handleNewAgreementsRoute',
         '_handlePluginListFilterOffsetRoute',
         '_handlePluginListFilterRoute',
         '_handlePluginListOffsetRoute',
@@ -532,7 +533,14 @@
       });
 
       test('_handleAgreementsRoute', () => {
-        element._handleAgreementsRoute({params: {}});
+        const data = {params: {}};
+        element._handleAgreementsRoute(data);
+        assert.isTrue(redirectStub.calledOnce);
+        assert.equal(redirectStub.lastCall.args[0], '/settings/#Agreements');
+      });
+
+      test('_handleNewAgreementsRoute', () => {
+        element._handleNewAgreementsRoute({params: {}});
         assert.isTrue(setParamsStub.calledOnce);
         assert.equal(setParamsStub.lastCall.args[0].view,
             Gerrit.Nav.View.AGREEMENTS);
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 95cddab..7b279c7 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -179,7 +179,7 @@
         </gr-endpoint-decorator>
       </template>
       <template is="dom-if" if="[[_showCLAView]]" restamp="true">
-        <gr-cla-view path="[[_path]]"></gr-cla-view>
+        <gr-cla-view></gr-cla-view>
       </template>
       <div id="errorView" class="errorView">
         <div class="errorEmoji">[[_lastError.emoji]]</div>
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
index c665df4..307a2b4 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
@@ -53,8 +53,7 @@
           </template>
         </tbody>
       </table>
-      <!-- TODO: Renable this when supported in polygerrit -->
-      <!-- <a href$="[[getUrl()]]">New Contributor Agreement</a> -->
+      <a href$="[[getUrl()]]">New Contributor Agreement</a>
     </div>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html
index b667d66..a1f5dc5 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2017 The Android Open Source Project
+Copyright (C) 2018 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.
@@ -14,12 +14,94 @@
 limitations under the License.
 -->
 
+<link rel="import" href="../../../behaviors/base-url-behavior/base-url-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-placeholder/gr-placeholder.html">
+<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-cla-view">
   <template>
-    <gr-placeholder title="Agreements" path="[[path]]"></gr-placeholder>
+    <style include="shared-styles">
+      h1 {
+        margin-bottom: .6em;
+      }
+      h3 {
+        margin-bottom: .5em;
+      }
+      .agreementsUrl {
+        border: 0.1em solid #b0bdcc;
+        margin-bottom: 1.25em;
+        margin-left: 1.25em;
+        margin-right: 1.25em;
+        padding: 0.3em;
+      }
+      #claNewAgreementsLabel {
+        font-family: var(--font-family-bold);
+      }
+      #claNewAgreement {
+        display: none;
+      }
+      #claNewAgreement.show {
+        display: block;
+      }
+      .contributorAgreementButton {
+        font-family: var(--font-family-bold);
+      }
+      .contributorAgreementAlreadySubmitted {
+        color: red;
+        margin: 0 2em;
+        padding: .5em;
+      }
+      .agreementsSubmitted,
+      .hideAgreementsTextBox {
+        display: none;
+      }
+      main {
+        margin: 2em auto;
+        max-width: 50em;
+      }
+    </style>
+    <style include="gr-form-styles"></style>
+    <main>
+      <h1>New Contributor Agreement</h1>
+      <h3>Select an agreement type:</h3>
+      <template is="dom-repeat" items="[[_serverConfig.auth.contributor_agreements]]">
+        <span class="contributorAgreementButton">
+          <input id$="claNewAgreementsInput[[item.name]]"
+              name="claNewAgreementsRadio"
+              type="radio"
+              data-name$="[[item.name]]"
+              data-url$="[[item.url]]"
+              on-tap="_handleShowAgreement"
+              disabled$="[[_disableAggreements(item, _groups)]]">
+          <label id="claNewAgreementsLabel">[[item.name]]</label>
+        </span>
+        <div class$="contributorAgreementAlreadySubmitted [[_hideAggreements(item, _groups)]]">
+          Agreement already submitted.
+        </div>
+        <div class="agreementsUrl">
+          [[item.description]]
+        </div>
+      </template>
+      <div id="claNewAgreement" class$="[[_computeShowAgreementsClass(_showAgreements)]]">
+        <h3 class="smallHeading">Review the agreement:</h3>
+        <div id="agreementsUrl" class="agreementsUrl">
+          <a href$="[[_agreementsUrl]]" target="blank" rel="noopener">
+            Please review the agreement.</a>
+        </div>
+        <div class$="agreementsTextBox [[_computeHideAgreementClass(_agreementName, _serverConfig.auth.contributor_agreements)]]">
+          <h3 class="smallHeading">Complete the agreement:</h3>
+          <input id="input-agreements" is="iron-input" bind-value="{{_agreementsText}}" placeholder="Enter 'I agree' here" />
+          <gr-button on-tap="_handleSaveAgreements" disabled="[[_disableAgreementsText(_agreementsText)]]">
+            Submit
+          </gr-button>
+        </div>
+      </div>
+    </main>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-cla-view.js"></script>
-</dom-module>
+</dom-module>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
index 71dc71b..39400c64 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2018 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.
@@ -18,7 +18,121 @@
     is: 'gr-cla-view',
 
     properties: {
-      path: String,
+      _groups: Object,
+      /** @type {?} */
+      _serverConfig: Object,
+      _agreementsText: String,
+      _agreementName: String,
+      _showAgreements: {
+        type: Boolean,
+        value: false,
+      },
+      _agreementsUrl: String,
+    },
+
+    behaviors: [
+      Gerrit.BaseUrlBehavior,
+    ],
+
+    attached() {
+      this.loadData();
+
+      this.fire('title-change', {title: 'New Contributor Agreement'});
+    },
+
+    loadData() {
+      const promises = [];
+      promises.push(this.$.restAPI.getConfig(true).then(config => {
+        this._serverConfig = config;
+      }));
+
+      promises.push(this.$.restAPI.getAccountGroups().then(groups => {
+        this._groups = groups.sort((a, b) => {
+          return a.name.localeCompare(b.name);
+        });
+      }));
+
+      return Promise.all(promises);
+    },
+
+    _getAgreementsUrl(configUrl) {
+      let url;
+      if (!configUrl) { return ''; }
+      if (configUrl.startsWith('http:') || configUrl.startsWith('https:')) {
+        url = configUrl;
+      } else {
+        url = this.getBaseUrl() + '/' + configUrl;
+      }
+
+      return url;
+    },
+
+    _handleShowAgreement(e) {
+      this._agreementName = e.target.getAttribute('data-name');
+      this._agreementsUrl =
+          this._getAgreementsUrl(e.target.getAttribute('data-url'));
+      this._showAgreements = true;
+    },
+
+    _handleSaveAgreements(e) {
+      this._createToast('Agreement saving...');
+
+      const name = this._agreementName;
+      return this.$.restAPI.saveAccountAgreement({name}).then(res => {
+        let message = 'Agreement failed to be submitted, please try again';
+        if (res.status === 200) {
+          message = 'Agreement has been successfully submited.';
+        }
+        this._createToast(message);
+        this.loadData();
+        this._agreementsText = '';
+        this._showAgreements = false;
+      });
+    },
+
+    _createToast(message) {
+      this.dispatchEvent(new CustomEvent('show-alert',
+          {detail: {message}, bubbles: true}));
+    },
+
+    _computeShowAgreementsClass(agreements) {
+      return agreements ? 'show' : '';
+    },
+
+    _disableAggreements(item, groups) {
+      for (const value of groups) {
+        if (item && item.auto_verify_group &&
+            item.auto_verify_group.name === value.name) {
+          return true;
+        }
+      }
+
+      return false;
+    },
+
+    _hideAggreements(item, groups) {
+      return this._disableAggreements(item, groups) ?
+          '' : 'agreementsSubmitted';
+    },
+
+    _disableAgreementsText(text) {
+      return text.toLowerCase() === 'i agree' ? false : true;
+    },
+
+    // This checks for auto_verify_group,
+    // if specified it returns 'hideAgreementsTextBox' which
+    // then hides the text box and submit button.
+    _computeHideAgreementClass(name, config) {
+      for (const key in config) {
+        if (!config.hasOwnProperty(key)) { return; }
+        for (const prop in config[key]) {
+          if (!config[key].hasOwnProperty(prop)) { return; }
+          if (name === config[key].name &&
+              !config[key].auto_verify_group) {
+            return 'hideAgreementsTextBox';
+          }
+        }
+      }
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
new file mode 100644
index 0000000..985fbfa
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2018 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-cla-view</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-cla-view.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-cla-view></gr-cla-view>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-cla-view tests', () => {
+    let element;
+    let agreements;
+    const auth = {
+      name: 'Individual',
+      description: 'test-description',
+      url: 'static/cla_individual.html',
+      auto_verify_group: {
+        url: '#/admin/groups/uuid-bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
+        options: {
+          visible_to_all: true,
+        },
+        group_id: 20,
+        owner: 'CLA Accepted - Individual',
+        owner_id: 'bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
+        created_on: '2017-07-31 15:11:04.000000000',
+        id: 'bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
+        name: 'CLA Accepted - Individual',
+      },
+    };
+    const auth2 = {
+      name: 'Individual2',
+      description: 'test-description2',
+      url: 'static/cla_individual2.html',
+      auto_verify_group: {
+        url: '#/admin/groups/uuid-e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
+        options: {},
+        group_id: 21,
+        owner: 'CLA Accepted - Individual2',
+        owner_id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
+        created_on: '2017-07-31 15:25:42.000000000',
+        id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
+        name: 'CLA Accepted - Individual2',
+      },
+    };
+    const config = {
+      auth: {
+        use_contributor_agreements: true,
+        contributor_agreements: [
+          {
+            name: 'Individual',
+            description: 'test-description',
+            url: 'static/cla_individual.html',
+          },
+        ],
+      },
+    };
+    const config2 = {
+      auth: {
+        use_contributor_agreements: true,
+        contributor_agreements: [
+          {
+            name: 'Individual2',
+            description: 'test-description2',
+            url: 'static/cla_individual2.html',
+          },
+        ],
+      },
+    };
+    const groups = [
+      {
+        url: 'some url',
+        options: {},
+        description: 'Group 1 description',
+        group_id: 1,
+        owner: 'Administrators',
+        owner_id: '123',
+        id: 'abc',
+        name: 'Individual',
+      },
+      {
+        options: {visible_to_all: true},
+        id: '456',
+        group_id: 2,
+        name: 'Individual 2',
+      },
+      {
+        options: {visible_to_all: true},
+        id: '457',
+        group_id: 3,
+        name: 'CLA Accepted - Individual',
+      },
+    ];
+
+    setup(done => {
+      agreements = [{
+        url: 'test-agreements.html',
+        description: 'Agreements 1 description',
+        name: 'Agreements 1',
+      }];
+
+      stub('gr-rest-api-interface', {
+        getAccountGroups() { return Promise.resolve(agreements); },
+      });
+
+      element = fixture('basic');
+
+      element.loadData().then(() => { flush(done); });
+    });
+
+    test('_disableAggreements equals true', () => {
+      assert.isTrue(element._disableAggreements(auth, groups));
+    });
+
+    test('_disableAggreements equals false', () => {
+      assert.isFalse(element._disableAggreements(auth2, groups));
+    });
+
+    test('_hideAggreements equals string', () => {
+      assert.equal(element._hideAggreements(auth, groups), '');
+    });
+
+    test('_hideAggreements equals agreementsSubmitted', () => {
+      assert.equal(element._hideAggreements(auth2, groups),
+          'agreementsSubmitted');
+    });
+
+    test('_disableAgreementsText equals true', () => {
+      assert.isFalse(element._disableAgreementsText('I AGREE'));
+    });
+
+    test('_disableAgreementsText equals true', () => {
+      assert.isTrue(element._disableAgreementsText('I DO NOT AGREE'));
+    });
+
+    test('_computeHideAgreementClass returns true', () => {
+      assert.equal(
+          element._computeHideAgreementClass(
+              auth.name, config.auth.contributor_agreements),
+          'hideAgreementsTextBox');
+    });
+
+    test('_computeHideAgreementClass returns undefined', () => {
+      assert.isUndefined(
+          element._computeHideAgreementClass(
+              auth.name, config2.auth.contributor_agreements));
+    });
+
+    test('_getAgreementsUrl has http', () => {
+      assert.equal(element._getAgreementsUrl(
+          'http://test.org/test.html'), 'http://test.org/test.html');
+    });
+
+    test('_getAgreementsUrl does not have http://', () => {
+      assert.equal(element._getAgreementsUrl(
+          'test_cla.html'), '/test_cla.html');
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.html b/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.html
deleted file mode 100644
index 15f44cf..0000000
--- a/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<!--
-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="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../styles/shared-styles.html">
-
-<dom-module id="gr-placeholder">
-  <template>
-    <style include="shared-styles">
-      main {
-        margin: 2em auto;
-        max-width: 46em;
-      }
-      h1 {
-        margin-bottom: .1em;
-      }
-      @media only screen and (max-width: 67em) {
-        main {
-          margin: 2em 0 2em 15em;
-        }
-      }
-      @media only screen and (max-width: 53em) {
-        .loading {
-          padding: 0 var(--default-horizontal-margin);
-        }
-        main {
-          margin: 2em 1em;
-        }
-      }
-    </style>
-    <main>
-      <h1>[[title]]</h1>
-      <section>
-        This page is not yet implemented in PolyGerrit. View it in the
-        <a id="gwtLink" href$="[[computeGwtUrl(path)]]" rel="external">
-        Old UI</a>
-      </section>
-    </main>
-  </template>
-  <script src="gr-placeholder.js"></script>
-</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.js b/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.js
deleted file mode 100644
index 9b60061..0000000
--- a/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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';
-
-  Polymer({
-    is: 'gr-placeholder',
-
-    properties: {
-      path: String,
-      title: String,
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-    ],
-  });
-})();
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index a3d3d90..b5bb35a 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -230,8 +230,12 @@
       return JSON.parse(source.substring(JSON_PREFIX.length));
     },
 
-    getConfig() {
-      return this._fetchSharedCacheURL('/config/server/info');
+    getConfig(noCache) {
+      if (!noCache) {
+        return this._fetchSharedCacheURL('/config/server/info');
+      }
+
+      return this.fetchJSON('/config/server/info');
     },
 
     getRepo(repo) {
@@ -675,13 +679,17 @@
     },
 
     getAccountGroups() {
-      return this._fetchSharedCacheURL('/accounts/self/groups');
+      return this.fetchJSON('/accounts/self/groups');
     },
 
     getAccountAgreements() {
       return this._fetchSharedCacheURL('/accounts/self/agreements');
     },
 
+    saveAccountAgreement(name) {
+      return this.send('PUT', '/accounts/self/agreements', name);
+    },
+
     /**
      * @param {string=} opt_params
      */
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index a21890c..44a9296 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -119,6 +119,7 @@
     'plugins/gr-settings-api/gr-settings-api_test.html',
     'settings/gr-account-info/gr-account-info_test.html',
     'settings/gr-change-table-editor/gr-change-table-editor_test.html',
+    'settings/gr-cla-view/gr-cla-view_test.html',
     'settings/gr-edit-preferences/gr-edit-preferences_test.html',
     'settings/gr-email-editor/gr-email-editor_test.html',
     'settings/gr-group-list/gr-group-list_test.html',