From 555356ced901976b4645daa3f894fb44143ee803 Mon Sep 17 00:00:00 2001
From: Romain Courteaud <romain@nexedi.com>
Date: Mon, 17 Sep 2018 10:39:27 +0200
Subject: [PATCH] [erp5_web_renderjs_ui] RelationField: add checkValidity
 method

Prevent submitting the form if the relation field contains an unknown search string.
---
 ...gadget_erp5_multirelationstringfield_js.js | 46 ++++++++++++++++-
 ...adget_erp5_multirelationstringfield_js.xml |  4 +-
 .../rjs_gadget_erp5_relation_input_js.js      | 17 +++++--
 .../rjs_gadget_erp5_relation_input_js.xml     |  4 +-
 .../rjs_gadget_erp5_relationstringfield_js.js |  7 +++
 ...rjs_gadget_erp5_relationstringfield_js.xml |  4 +-
 .../testFormViewEditableSaveFail.zpt          | 51 +++----------------
 .../testFormViewEditablePersistent.zpt        | 46 ++++++++---------
 ...testRelationFieldNothingSelectedInList.zpt | 34 +++----------
 .../Zuite_CommonTemplateForRenderjsUi.zpt     | 10 +++-
 10 files changed, 114 insertions(+), 109 deletions(-)

diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multirelationstringfield_js.js b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multirelationstringfield_js.js
index 8810e748fd..9775daad15 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multirelationstringfield_js.js
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multirelationstringfield_js.js
@@ -58,7 +58,7 @@
           url: field_json.url,
           allow_creation: field_json.allow_creation,
           portal_types: field_json.portal_types,
-	  translated_portal_types: field_json.translated_portal_types,
+          translated_portal_types: field_json.translated_portal_types,
           relation_field_id: field_json.relation_field_id,
           hidden: field_json.hidden,
           // Force calling subfield render
@@ -234,7 +234,8 @@
               }
             }
             //user remove all data
-            if (options.format === "erp5" && result[gadget.state.key].length === 0) {
+            if (options.format === "erp5" &&
+                result[gadget.state.key].length === 0) {
               result[gadget.state.key] = "";
             }
             return result;
@@ -242,6 +243,47 @@
           });
       }
       return final_result;
+    }, {mutex: 'changestate'})
+
+    .declareMethod('checkValidity', function () {
+      var context = this;
+
+      function checkSubContentValidity(node) {
+        var scope = node.getAttribute('data-gadget-scope');
+        if (scope !== null) {
+          return context.getDeclaredGadget(
+            node.getAttribute('data-gadget-scope')
+          )
+            .push(function (result) {
+              return result.checkValidity();
+            });
+        }
+      }
+
+      if (this.state.editable) {
+        return new RSVP.Queue()
+          .push(function () {
+            var promise_list = [],
+              i;
+            for (i = 0; i < context.element.childNodes.length; i += 1) {
+              promise_list.push(
+                checkSubContentValidity(context.element.childNodes[i])
+              );
+            }
+            return RSVP.all(promise_list);
+          })
+          .push(function (validity_list) {
+            var i;
+            for (i = 0; i < validity_list.length; i += 1) {
+              if (!validity_list[i]) {
+                return false;
+              }
+            }
+            return true;
+          });
+      }
+      return true;
+
     }, {mutex: 'changestate'});
 
 }(window, rJS, RSVP, document));
diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multirelationstringfield_js.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multirelationstringfield_js.xml
index d6a96f3b29..980c3ad92b 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multirelationstringfield_js.xml
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multirelationstringfield_js.xml
@@ -230,7 +230,7 @@
             </item>
             <item>
                 <key> <string>serial</string> </key>
-                <value> <string>965.241.13759.3805</string> </value>
+                <value> <string>970.11252.5060.21026</string> </value>
             </item>
             <item>
                 <key> <string>state</string> </key>
@@ -248,7 +248,7 @@
                     </tuple>
                     <state>
                       <tuple>
-                        <float>1517321450.32</float>
+                        <float>1536588738.44</float>
                         <string>UTC</string>
                       </tuple>
                     </state>
diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relation_input_js.js b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relation_input_js.js
index b9072f774c..e697a5946a 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relation_input_js.js
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relation_input_js.js
@@ -445,8 +445,18 @@
     }, true, false)
 
     .declareMethod('checkValidity', function () {
+      if ((this.state.value_text) && (
+          (this.state.value_relative_url === null) &&
+            (this.state.value_uid === null) &&
+            (this.state.value_portal_type === null)
+        )) {
+        return this.notifyInvalid("No such document was found")
+          .push(function () {
+            return false;
+          });
+      }
       return true;
-    })
+    }, {mutex: 'changestate'})
 
     // XXX Use html5 input
     .onEvent('invalid', function (evt) {
@@ -455,10 +465,7 @@
     }, true, false)
 
     .onEvent('change', function () {
-      return RSVP.all([
-        this.checkValidity(),
-        this.notifyChange()
-      ]);
+      return this.notifyChange();
     }, false, false)
 
 
diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relation_input_js.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relation_input_js.xml
index afc7910653..3f112e364b 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relation_input_js.xml
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relation_input_js.xml
@@ -236,7 +236,7 @@
             </item>
             <item>
                 <key> <string>serial</string> </key>
-                <value> <string>966.58749.49498.3481</string> </value>
+                <value> <string>970.16829.43481.45789</string> </value>
             </item>
             <item>
                 <key> <string>state</string> </key>
@@ -254,7 +254,7 @@
                     </tuple>
                     <state>
                       <tuple>
-                        <float>1523882792.37</float>
+                        <float>1536923390.01</float>
                         <string>UTC</string>
                       </tuple>
                     </state>
diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relationstringfield_js.js b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relationstringfield_js.js
index 0c5815b06a..3874a6a6bf 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relationstringfield_js.js
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relationstringfield_js.js
@@ -85,6 +85,13 @@
           }
           return result;
         });
+    }, {mutex: 'changestate'})
+
+    .declareMethod('checkValidity', function () {
+      return this.getDeclaredGadget("relation_input")
+        .push(function (input_gadget) {
+          return input_gadget.checkValidity();
+        });
     }, {mutex: 'changestate'});
 
 }(window, rJS));
\ No newline at end of file
diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relationstringfield_js.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relationstringfield_js.xml
index fbe669e8b7..35cc8a2915 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relationstringfield_js.xml
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_relationstringfield_js.xml
@@ -230,7 +230,7 @@
             </item>
             <item>
                 <key> <string>serial</string> </key>
-                <value> <string>963.11788.48702.26146</string> </value>
+                <value> <string>970.11323.28526.22766</string> </value>
             </item>
             <item>
                 <key> <string>state</string> </key>
@@ -248,7 +248,7 @@
                     </tuple>
                     <state>
                       <tuple>
-                        <float>1516352110.99</float>
+                        <float>1536593071.17</float>
                         <string>UTC</string>
                       </tuple>
                     </state>
diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_notification_zuite/testFormViewEditableSaveFail.zpt b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_notification_zuite/testFormViewEditableSaveFail.zpt
index a3fa4dae0c..dc2e105e4c 100644
--- a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_notification_zuite/testFormViewEditableSaveFail.zpt
+++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_notification_zuite/testFormViewEditableSaveFail.zpt
@@ -17,60 +17,25 @@
   <td></td>
 </tr>
 
-<!-- Wait for gadget to be loaded -->
-<tr>
-  <td>waitForElementPresent</td>
-  <td>//div[@data-gadget-url='${base_url}/web_site_module/renderjs_runner/gadget_erp5_pt_form_view_editable.html']</td>
-  <td></td>
-</tr>
-<tr>
-  <td>waitForTextPresent</td>
-  <td>Title 1</td>
-  <td></td>
-</tr>
+<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_app_loaded" />
 
-<!-- Header has a save button -->
-<tr>
-  <td>assertElementPresent</td>
-  <td>//div[@data-gadget-scope='header']//button[text()='Save' and @type='submit']</td>
-  <td></td>
-</tr>
+<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/go_to_foo_relation_field_view" />
 <tr>
   <td>type</td>
-  <td>field_my_foo_category_title</td>
-  <td>QWERTY</td>
-</tr>
-<tr>
-  <td>fireEvent</td>
-  <td>//input[@name='field_my_foo_category_title']</td>
-  <td>input</td>
-</tr>
-<tr>
-  <td>pause</td>
-  <td>1000</td>
-  <td></td>
-</tr>
-<tr>
-  <td>click</td>
-  <td>//div[@data-gadget-scope='header']//button[text()='Save' and @type='submit']</td>
-  <td></td>
+  <td>field_my_title</td>
+  <td>Foo Title 123</td>
 </tr>
+<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/click_save" />
 
-
-<!--tr>
-  <td>waitForElementPresent</td>
-  <td>//div[@data-gadget-scope='header']//button[text()='Save' and @type='submit' and contains(@class, 'ui-icon-spinner')]</td>
-  <td></td>
-</tr-->
 <tr>
   <td>waitForTextPresent</td>
-  <td>No such document was found</td>
+  <td>Input is required but no input given.</td>
   <td></td>
 </tr>
 <tr>
   <td>verifyValue</td>
-  <td>//input[@name='field_my_foo_category_title']</td>
-  <td>QWERTY</td>
+  <td>//input[@name='field_my_title']</td>
+  <td>Foo Title 123</td>
 </tr>
 
 <tal:block tal:define="notification_configuration python: {'class': 'error',
diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_page_templates_zuite/testFormViewEditablePersistent.zpt b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_page_templates_zuite/testFormViewEditablePersistent.zpt
index 055f93506b..53bff83c13 100644
--- a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_page_templates_zuite/testFormViewEditablePersistent.zpt
+++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_page_templates_zuite/testFormViewEditablePersistent.zpt
@@ -21,27 +21,24 @@
     <td>renderjs_url</td></tr>
 <tr><td>open</td>
     <td>${renderjs_url}/#/foo_module/1?editable=true</td><td></td></tr>
+<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_app_loaded" />
 <tr><td>waitForElementPresent</td>
     <td>//div[@data-gadget-url='${renderjs_url}/gadget_erp5_pt_form_view_editable.html']</td><td></td></tr>
 <tr><td>verifyElementNotPresent</td>
     <td>//div[@data-gadget-url='${renderjs_url}/gadget_erp5_pt_form_view.html']</td><td></td></tr>
 
-<!-- Unsuccessful save does not mingle with editability -->
+<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/go_to_foo_relation_field_view" />
 <tr><td>waitForElementPresent</td>
-    <td>//input[@name='field_my_foo_category_title']</td><td></td></tr>
-<tr><td>type</td>
-    <td>//input[@name='field_my_foo_category_title']</td>
-    <td>QWERTY</td></tr>
-<tr><td>fireEvent</td>
-    <td>//input[@name='field_my_foo_category_title']</td>
-    <td>input</td></tr>
-<tr><td>waitForElementPresent</td>
-    <td>//div[@data-gadget-scope='header']//button[text()='Save' and @type='submit']</td><td></td></tr>
-<tr><td>click</td>
-    <td>//div[@data-gadget-scope='header']//button[text()='Save' and @type='submit']</td><td></td></tr>
-<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
-<tr><td>waitForTextPresent</td>
-    <td>Input data has errors.</td><td></td></tr>
+    <td>//div[@data-gadget-url='${renderjs_url}/gadget_erp5_pt_form_view_editable.html']</td><td></td></tr>
+<tr><td>verifyElementNotPresent</td>
+    <td>//div[@data-gadget-url='${renderjs_url}/gadget_erp5_pt_form_view.html']</td><td></td></tr>
+
+<!-- Unsuccessful save does not mingle with editability -->
+<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/click_save" />
+<tal:block tal:define="notification_configuration python: {'class': 'error',
+                                                           'text': 'Input data has errors.'}">
+  <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
+</tal:block>
 
 <tr><td>waitForElementPresent</td>
     <td>//div[@data-gadget-url='${renderjs_url}/gadget_erp5_pt_form_view_editable.html']</td><td></td></tr>
@@ -54,22 +51,23 @@
 <tr><td>fireEvent</td>
     <td>//input[@name='field_my_foo_category_title']</td>
     <td>focus</td></tr>
-<tr><td>waitForElementPresent</td>
-    <td>//div[@data-gadget-scope='field_my_foo_category_title']//li</td><td></td></tr>
 <tr><td>type</td>
     <td>//input[@name='field_my_foo_category_title']</td>
-    <td></td></tr>
+    <td>object_exchange</td></tr>
 <tr><td>fireEvent</td>
     <td>//input[@name='field_my_foo_category_title']</td>
     <td>input</td></tr>
-<tr><td>fireEvent</td>
-    <td>//input[@name='field_my_foo_category_title']</td>
-    <td>blur</td></tr>
 <tr>
   <td>waitForElementPresent</td>
-  <td>//button[@data-i18n='Save'][contains(@class, 'ui-icon-warning')]</td>
+  <td>//li[@data-relative-url='portal_categories/action_type/object_exchange']</td>
   <td></td>
 </tr>
+<tr>
+  <td>click</td>
+  <td>//li[@data-relative-url='portal_categories/action_type/object_exchange']</td>
+  <td></td>
+</tr>
+
 <tr><td>waitForElementPresent</td>
     <td>//input[@name='field_my_title']</td><td></td></tr>
 <tr><td>type</td>
@@ -98,8 +96,8 @@
 <tr><td>type</td>
     <td>//div[@data-gadget-url='${renderjs_url}/gadget_erp5_pt_form_dialog.html']//textarea</td>
     <td>QWERTY</td></tr>
-<tr><td>click</td>
-    <td>//div[@data-gadget-url='${renderjs_url}/gadget_erp5_pt_form_dialog.html']//input[@type='submit']</td><td></td></tr>
+<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/submit_dialog" />
+
 <tal:block tal:define="notification_configuration python: {'class': 'success',
                                                            'text': 'Status changed.'}">
   <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_relation_field_zuite/testRelationFieldNothingSelectedInList.zpt b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_relation_field_zuite/testRelationFieldNothingSelectedInList.zpt
index 6f887b2b34..ec63c3eab5 100644
--- a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_relation_field_zuite/testRelationFieldNothingSelectedInList.zpt
+++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_relation_field_zuite/testRelationFieldNothingSelectedInList.zpt
@@ -29,8 +29,10 @@
   <td>${base_url}/web_site_module/renderjs_runner/#/foo_module</td>
   <td></td>
 </tr>
+<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_app_loaded" />
+
 <tr>
-  <td>waitForElementPresent</td>
+  <td>assertElementPresent</td>
   <td>//a[@data-i18n='Add']</td>
   <td></td>
 </tr>
@@ -47,26 +49,8 @@
 </tal:block>
 <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
 
-<tr>
-  <td>waitForTextPresent</td>
-  <td>Save</td>
-  <td></td>
-</tr>
-<tr>
-  <td>assertTextPresent</td>
-  <td>Quantity</td>
-  <td></td>
-</tr>
-
 <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/go_to_foo_relation_field_view" />
 
-
-<tr>
-  <td>waitForElementPresent</td>
-  <td>//input[@name='field_my_successor_title']</td>
-  <td></td>
-</tr>
-
 <tr>
   <td>type</td>
   <td>//input[@name='field_my_successor_title']</td>
@@ -108,19 +92,15 @@
   <td>A new foo</td>
 </tr>
 
+<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/click_save" />
 
-<tr>
- <td>click</td>
- <td>//button[@data-i18n='Save']</td>
- <td></td>
-</tr>
-<tal:block tal:define="notification_configuration python: {'class': 'error',
+<!--tal:block tal:define="notification_configuration python: {'class': 'error',
                                                            'text': 'Input data has errors.'}">
   <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
-</tal:block>
+</tal:block-->
 <tr>
   <td>assertElementPresent</td>
-  <td>//div[@data-gadget-scope='field_my_successor_title']//span[text()='No such document was found.']</td>
+  <td>//div[@data-gadget-scope='field_my_successor_title']//span[text()='No such document was found']</td>
   <td></td>
 </tr>
 
diff --git a/bt5/erp5_web_renderjs_ui_test/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui_test/Zuite_CommonTemplateForRenderjsUi.zpt b/bt5/erp5_web_renderjs_ui_test/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui_test/Zuite_CommonTemplateForRenderjsUi.zpt
index 3273dfeaa3..75d7a46a7f 100644
--- a/bt5/erp5_web_renderjs_ui_test/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui_test/Zuite_CommonTemplateForRenderjsUi.zpt
+++ b/bt5/erp5_web_renderjs_ui_test/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui_test/Zuite_CommonTemplateForRenderjsUi.zpt
@@ -1,8 +1,8 @@
 <tal:block xmlns:tal="http://xml.zope.org/namespaces/tal"
            xmlns:metal="http://xml.zope.org/namespaces/metal">
-  <tal:block metal:define-macro="save">
+  <tal:block metal:define-macro="click_save">
     <tr>
-      <td colspan="3"><b>Save</b></td>
+      <td colspan="3"><b>Click on Save</b></td>
     </tr>
     <tr>
      <td>waitForElementPresent</td>
@@ -29,6 +29,12 @@
      <td>//button[@data-i18n='Save']</td>
      <td></td>
     </tr>
+  </tal:block>
+  <tal:block metal:define-macro="save">
+    <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/click_save" />
+    <tr>
+      <td colspan="3"><b>Save</b></td>
+    </tr>
     <!-- First loader while calling Base_edit -->
     <tr>
       <td>waitForElementPresent</td>
-- 
2.30.9