This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch 
feature/WW-5695-html5-constraint-validation
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 79e27726f4d9967a778b7bcf1ac1f838e11a7ad4
Author: Lukasz Lenart <[email protected]>
AuthorDate: Mon Aug 24 23:46:39 2026 +0200

    WW-5695 feat(html5): render derived constraint attributes
    
    Included from common-attributes.ftl so every html5 input picks the 
attributes
    up without per-template edits. Values rely on FreeMarker's auto-escaping
    (HTMLOutputFormat + ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, set globally by
    FreemarkerManager): pattern and the data-msg-* text are author-controlled 
and
    land inside an attribute, and the ?html builtin is rejected at parse time
    under this configuration ("legacy escaping ... not allowed when
    auto-escaping is on with a markup output format") because ${...} is already
    escaped. Verified against the actual template with a standalone FreeMarker
    render using the same Configuration: '"><script>&' comes out as
    &quot;&gt;&lt;script&gt;&amp;.
    
    Covers the regression that matters most - requiredLabel draws an asterisk 
and
    must never emit a required attribute.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 .../resources/template/html5/common-attributes.ftl |  1 +
 .../{common-attributes.ftl => constraints.ftl}     |  4 +-
 .../views/jsp/ui/Html5ConstraintRenderingTest.java | 82 ++++++++++++++++++++++
 3 files changed, 84 insertions(+), 3 deletions(-)

diff --git a/core/src/main/resources/template/html5/common-attributes.ftl 
b/core/src/main/resources/template/html5/common-attributes.ftl
index 424316dad..d8730c23a 100644
--- a/core/src/main/resources/template/html5/common-attributes.ftl
+++ b/core/src/main/resources/template/html5/common-attributes.ftl
@@ -21,3 +21,4 @@
 <#if attributes.accesskey?has_content>
  accesskey="${attributes.accesskey}"<#rt/>
 </#if>
+<#include 
"/${attributes.templateDir}/${attributes.expandTheme}/constraints.ftl" /><#rt/>
diff --git a/core/src/main/resources/template/html5/common-attributes.ftl 
b/core/src/main/resources/template/html5/constraints.ftl
similarity index 84%
copy from core/src/main/resources/template/html5/common-attributes.ftl
copy to core/src/main/resources/template/html5/constraints.ftl
index 424316dad..7ff272c30 100644
--- a/core/src/main/resources/template/html5/common-attributes.ftl
+++ b/core/src/main/resources/template/html5/constraints.ftl
@@ -18,6 +18,4 @@
  * under the License.
  */
 -->
-<#if attributes.accesskey?has_content>
- accesskey="${attributes.accesskey}"<#rt/>
-</#if>
+<#if attributes.constraints??><#list attributes.constraints as attributeName, 
attributeValue> ${attributeName}="${attributeValue}"<#rt/></#list></#if>
diff --git 
a/core/src/test/java/org/apache/struts2/views/jsp/ui/Html5ConstraintRenderingTest.java
 
b/core/src/test/java/org/apache/struts2/views/jsp/ui/Html5ConstraintRenderingTest.java
new file mode 100644
index 000000000..f101f4a8b
--- /dev/null
+++ 
b/core/src/test/java/org/apache/struts2/views/jsp/ui/Html5ConstraintRenderingTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.struts2.views.jsp.ui;
+
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.TestConfigurationProvider;
+import org.apache.struts2.mock.MockActionProxy;
+import org.apache.struts2.views.jsp.AbstractUITagTest;
+
+import java.util.HashMap;
+
+public class Html5ConstraintRenderingTest extends AbstractUITagTest {
+
+    public void testRendersConstraintAttributes() throws Exception {
+        String output = render("true");
+
+        assertTrue("expected minlength in: " + output, 
output.contains("minlength=\"3\""));
+    }
+
+    public void testRendersNothingWhenTheConstantIsOff() throws Exception {
+        String output = render("false");
+
+        assertFalse("expected no minlength in: " + output, 
output.contains("minlength="));
+    }
+
+    public void testRequiredLabelDoesNotBecomeARequiredAttribute() throws 
Exception {
+        String output = render("true", "username", "true");
+
+        assertTrue("expected minlength in: " + output, 
output.contains("minlength=\"3\""));
+        assertFalse("requiredLabel draws an asterisk; it must never emit a 
required attribute: " + output,
+            output.contains("required=\"required\""));
+    }
+
+    private String render(String constraintsEnabled) throws Exception {
+        return render(constraintsEnabled, "username", null);
+    }
+
+    private String render(String constraintsEnabled, String fieldName, String 
requiredLabel) throws Exception {
+        initDispatcher(new HashMap<String, String>() {{
+            put("configProviders", TestConfigurationProvider.class.getName());
+            put(StrutsConstants.STRUTS_UI_HTML5_CONSTRAINTS, 
constraintsEnabled);
+        }});
+        createMocks();
+        ((MockActionProxy) 
actionProxy).setConfig(configuration.getRuntimeConfiguration().getActionConfig("",
 "constraintAction"));
+
+        FormTag form = new FormTag();
+        form.setPageContext(pageContext);
+        form.setTheme("html5");
+        form.setAction("constraintAction");
+        form.setNamespace("");
+        form.doStartTag();
+
+        TextFieldTag field = new TextFieldTag();
+        field.setPageContext(pageContext);
+        field.setTheme("html5");
+        field.setName(fieldName);
+        if (requiredLabel != null) {
+            field.setRequiredLabel(requiredLabel);
+        }
+        field.doStartTag();
+        field.doEndTag();
+        form.doEndTag();
+
+        return writer.toString();
+    }
+}

Reply via email to