Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,9 @@ When running tests, use this priority order:
### Specialized Agents

- `security-analyzer` - OGNL injection scanning, CVE detection
- `test-runner` - Maven test execution and coverage analysis
- `test-runner` - Maven test execution and coverage analysis, use this agent to RUN the tests
- `code-quality-checker` - JavaDoc compliance, pattern consistency
- `config-validator` - struts.xml validation, interceptor analysis
- `jakarta-migration-helper` - Jakarta EE migration assistance
- `codebase-analyzer` - Project structure and architecture analysis
- `codebase-locator` - Code and file location assistance
- `codebase-pattern-finder` - Pattern examples and usage
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.showcase.action;

import org.apache.struts2.ActionSupport;

public class Html5Action extends ActionSupport {

@Override
public String execute() throws Exception {
addActionError("Action error: only html5");
addActionMessage("Action message: only html5");
addFieldError("testField", "Field error: only html5");
return super.execute();
}
}
6 changes: 6 additions & 0 deletions apps/showcase/src/main/resources/struts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@
</action>
</package>

<package name="html5" extends="default" namespace="/html5">
<action name="index" class="org.apache.struts2.showcase.action.Html5Action">
<result>/WEB-INF/html5/index.jsp</result>
</action>
</package>

</struts>

<!-- END SNIPPET: xworkSample -->
Expand Down
3 changes: 3 additions & 0 deletions apps/showcase/src/main/webapp/WEB-INF/decorators/main.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@
<s:a href="%{#url}">Component Tag Example</s:a></li>
<li><s:url var="url" namespace="/tags/ui" action="actionTagExample" method="input"/><s:a
href="%{url}">Action Tag Example</s:a></li>
<li><s:url var="url" action="index"
namespace="/html5"/><s:a
href="%{#url}">Html 5 theme</s:a></li>
</ul>
</li>
<li class="dropdown">
Expand Down
77 changes: 77 additions & 0 deletions apps/showcase/src/main/webapp/WEB-INF/html5/index.jsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<!--
/*
* 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.
*/
-->
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html>
<html lang="en">
<head>
<s:url var="bootstrapCss" value="/styles/bootstrap.css" encode="false" includeParams="none"/>
<s:link theme="html5" href="%{bootstrapCss}"/>
<s:url var="mainCss" value="/styles/main.css" encode="false" includeParams="none"/>
<s:link theme="html5" href="%{mainCss}" />
<s:head />

Check warning on line 29 in apps/showcase/src/main/webapp/WEB-INF/html5/index.jsp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a <title> tag to this page.

See more on https://sonarcloud.io/project/issues?id=apache_struts&issues=AZrEoA5rFVHmaqjRYVX_&open=AZrEoA5rFVHmaqjRYVX_&pullRequest=1422
<title>Struts2 Showcase - Html5 theme</title>
</head>

<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="hero-unit">
<h1>Html 5 tags demo</h1>
<p>All the tags on this page are from <i>html5</i> theme. <s:a theme="html5" action="showcase" namespace="/">Back</s:a> to main Showcase App page
</div>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:a/&gt;</pre>
</div>
<div class="col-md-10">
<s:a theme="html5" action="index">index</s:a>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:actionerror/&gt;</pre>
</div>
<div class="col-md-10">
<s:actionerror theme="html5"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:actionmessage/&gt;</pre>
</div>
<div class="col-md-10">
<s:actionmessage theme="html5"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<pre>&lt;s:fielderror/&gt;</pre>
</div>
<div class="col-md-10">
<s:fielderror theme="html5"/>
</div>
</div>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions apps/showcase/src/main/webapp/WEB-INF/sitemesh3.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@
<mapping path="/images/*" exclude="true"/>
<mapping path="/static/*" exclude="true"/>
<mapping path="/nodecorate/*" exclude="true"/>
<mapping path="/html5/*" exclude="true"/>
</sitemesh>
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* 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 it.org.apache.struts2.showcase;

import org.htmlunit.WebClient;
import org.htmlunit.html.HtmlPage;
import org.junit.Assert;
import org.junit.Test;

/**
* Integration tests for HTML5 theme rendering in showcase application.
* <p>
* Tests validate that the HTML5 theme produces clean, semantic HTML5 markup
* without table-based layouts and properly displays action errors, action messages,
* and field errors.
*/
public class Html5TagExampleTest {

/**
* Tests basic HTML5 theme rendering and page load.
* <p>
* Verifies:
* - Page loads successfully (200 status)
* - HTML5 doctype is present
* - Page contains expected content
*/
@Test
public void testHtml5PageLoad() throws Exception {
try (final WebClient webClient = new WebClient()) {
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webClient.getOptions().setJavaScriptEnabled(false);

final HtmlPage page = webClient.getPage(
ParameterUtils.getBaseUrl() + "/html5/index.action"
);

Assert.assertEquals(200, page.getWebResponse().getStatusCode());

String pageContent = page.asNormalizedText();
Assert.assertTrue("Page should contain HTML5 demo title",
pageContent.contains("Html 5 tags demo"));
}
}

/**
* Tests HTML5 theme error and message display.
* <p>
* Verifies:
* - Action errors are displayed
* - Action messages are displayed
* - Field errors are displayed
* - Errors use clean HTML5 markup
*/
@Test
public void testHtml5ErrorDisplay() throws Exception {
try (final WebClient webClient = new WebClient()) {
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webClient.getOptions().setJavaScriptEnabled(false);

final HtmlPage page = webClient.getPage(
ParameterUtils.getBaseUrl() + "/html5/index.action"
);

String pageContent = page.asNormalizedText();

// Verify action error is displayed
Assert.assertTrue("Page should display action error",
pageContent.contains("Action error: only html5"));

// Verify action message is displayed
Assert.assertTrue("Page should display action message",
pageContent.contains("Action message: only html5"));

// Verify field error is displayed
Assert.assertTrue("Page should display field error",
pageContent.contains("Field error: only html5"));
}
}

/**
* Tests that HTML5 theme uses clean, semantic markup without tables.
* <p>
* Verifies:
* - No table-based layout for error messages
* - Uses semantic HTML5 elements
* - Error lists use &lt;ul&gt; elements
*/
@Test
public void testHtml5CleanMarkup() throws Exception {
try (final WebClient webClient = new WebClient()) {
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webClient.getOptions().setJavaScriptEnabled(false);

final HtmlPage page = webClient.getPage(
ParameterUtils.getBaseUrl() + "/html5/index.action"
);

String pageAsXml = page.asXml();

// HTML5 theme should use <ul> for error lists
Assert.assertTrue("Errors should be displayed in <ul> lists",
pageAsXml.contains("<ul"));

// Verify HTML5 theme does not use table-based layout for errors
Assert.assertFalse("HTML5 theme should not use table layout for errors",
pageAsXml.matches("(?s).*<table[^>]*>.*errorMessage.*</table>.*"));
}
}

/**
* Tests HTML5 theme anchor tag rendering.
* <p>
* Verifies:
* - Anchor tags are rendered correctly
* - Links have proper href attributes
* - HTML5 theme attributes are applied
*/
@Test
public void testHtml5AnchorTag() throws Exception {
try (final WebClient webClient = new WebClient()) {
webClient.getOptions().setJavaScriptEnabled(false);

final HtmlPage page = webClient.getPage(
ParameterUtils.getBaseUrl() + "/html5/index.action"
);

String pageContent = page.asNormalizedText();

// Verify anchor tag content is present
Assert.assertTrue("Page should contain 'index' link",
pageContent.contains("index"));

// Verify back link to showcase
Assert.assertTrue("Page should contain 'Back' link",
pageContent.contains("Back"));
}
}

/**
* Tests that HTML5 theme components are properly namespaced.
* <p>
* Verifies:
* - HTML5 action is accessible under /html5 namespace
* - Theme-specific rendering is applied
* - No conflicts with other themes
*/
@Test
public void testHtml5Namespace() throws Exception {
try (final WebClient webClient = new WebClient()) {
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);

final HtmlPage page = webClient.getPage(
ParameterUtils.getBaseUrl() + "/html5/index.action"
);

Assert.assertEquals("HTML5 action should return 200 status",
200, page.getWebResponse().getStatusCode());

String pageAsXml = page.asXml();

// Verify the page uses HTML5 theme by checking for theme-specific patterns
// HTML5 theme should not use table-based layouts
Assert.assertFalse("HTML5 theme should not use table layout for errors",
pageAsXml.matches("(?s).*<table[^>]*>.*errorMessage.*</table>.*"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public void renderTemplate(TemplateRenderingContext templateContext) throws Exce

model.put("tag", templateContext.getTag());
model.put("themeProperties", getThemeProps(templateContext.getTemplate()));


// the BodyContent JSP writer doesn't like it when FM flushes automatically --
// so let's just not do it (it will be flushed eventually anyway)
Expand Down
50 changes: 50 additions & 0 deletions core/src/main/resources/template/html5/a-close.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<#--
/*
* 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.
*/
-->
<#include "/${attributes.templateDir}/${attributes.expandTheme}/controlheader.ftl" />
<#compress>
<a
<#if attributes.id??>
id="${attributes.id}"
</#if>
<#if attributes.href??>
href="${attributes.href?no_esc}"
</#if>
<#if attributes.disabled!false>
disabled="disabled"
</#if>
<#if attributes.tabindex??>
tabindex="${attributes.tabindex}"
</#if>
<#if attributes.cssClass??>
class="${attributes.cssClass}"
</#if>
<#if attributes.cssStyle??>
style="${attributes.cssStyle}"
</#if>
<#if attributes.title??>
title="${attributes.title}"
</#if>
<#include "/${attributes.templateDir}/${attributes.expandTheme}/scripting-events.ftl" />
<#include "/${attributes.templateDir}/${attributes.expandTheme}/common-attributes.ftl" />
<#include "/${attributes.templateDir}/${attributes.expandTheme}/dynamic-attributes.ftl" />
>${tag.escapeHtmlBody()?then(attributes.body, attributes.body?no_esc)}</a>
</#compress>
<#include "/${attributes.templateDir}/${attributes.expandTheme}/controlfooter.ftl" />
Loading