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 "><script>&. 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(); + } +}
