View Javadoc
1   /*
2    * SPDX-License-Identifier: Apache-2.0
3    * See LICENSE file for details.
4    *
5    * Copyright 2018-2026 Hazendaz
6    * Copyright 2011-2018 tunyk
7    */
8   package com.tunyk.mvn.plugins.htmlcompressor;
9   
10  import java.io.StringReader;
11  import java.lang.reflect.Field;
12  import java.lang.reflect.InvocationTargetException;
13  import java.lang.reflect.Method;
14  import java.util.List;
15  
16  import javax.xml.parsers.DocumentBuilderFactory;
17  
18  import org.apache.maven.plugin.Mojo;
19  import org.apache.maven.plugin.MojoExecutionException;
20  import org.junit.jupiter.api.Assertions;
21  import org.junit.jupiter.api.Test;
22  import org.w3c.dom.Document;
23  import org.w3c.dom.Node;
24  import org.xml.sax.InputSource;
25  
26  /**
27   * Tests for generated plugin help mojo utilities.
28   */
29  class HelpMojoTest {
30  
31      /** The generated help mojo class name. */
32      private static final String HELP_MOJO_CLASS = "com.github.hazendaz.maven.htmlcompressor_maven_plugin.HelpMojo";
33  
34      @Test
35      void testExecuteNormalizesInvalidFormattingParameters() throws Exception {
36          Mojo mojo = (Mojo) newHelpMojo();
37          setField(mojo, "lineLength", -1);
38          setField(mojo, "indentSize", 0);
39          setField(mojo, "detail", true);
40  
41          Assertions.assertDoesNotThrow(mojo::execute);
42          Assertions.assertEquals(80, getField(mojo, "lineLength"));
43          Assertions.assertEquals(2, getField(mojo, "indentSize"));
44      }
45  
46      @Test
47      void testGetPropertyFromExpressionVariants() throws Exception {
48          Assertions.assertEquals("user.prop",
49                  invokeStatic("getPropertyFromExpression", new Class<?>[] { String.class }, "${user.prop}"));
50          Assertions.assertNull(invokeStatic("getPropertyFromExpression", new Class<?>[] { String.class }, "user.prop"));
51          Assertions.assertNull(
52                  invokeStatic("getPropertyFromExpression", new Class<?>[] { String.class }, "${outer${inner}}"));
53          Assertions
54                  .assertNull(invokeStatic("getPropertyFromExpression", new Class<?>[] { String.class }, (Object) null));
55      }
56  
57      @Test
58      void testNodeLookupUtilityBranches() throws Exception {
59          Document doc = parse("<root><item>first</item><item>second</item></root>");
60          Node root = doc.getDocumentElement();
61  
62          Assertions
63                  .assertNull(invokeStatic("findSingleChild", new Class<?>[] { Node.class, String.class }, root, "none"));
64  
65          ReflectiveOperationException duplicateSingleChild = Assertions.assertThrows(ReflectiveOperationException.class,
66                  () -> invokeStatic("findSingleChild", new Class<?>[] { Node.class, String.class }, root, "item"));
67          Assertions.assertInstanceOf(MojoExecutionException.class, duplicateSingleChild.getCause());
68  
69          ReflectiveOperationException missingGetSingleChild = Assertions.assertThrows(ReflectiveOperationException.class,
70                  () -> invokeStatic("getSingleChild", new Class<?>[] { Node.class, String.class }, root, "missing"));
71          Assertions.assertInstanceOf(MojoExecutionException.class, missingGetSingleChild.getCause());
72      }
73  
74      @SuppressWarnings("unchecked")
75      @Test
76      void testTextFormattingHelpers() throws Exception {
77          List<String> lines = (List<String>) invokeStatic("toLines",
78                  new Class<?>[] { String.class, int.class, int.class, int.class },
79                  "\talpha beta gamma\u00A0delta epsilon", 1, 2, 14);
80  
81          Assertions.assertTrue(lines.size() > 1, "Long text should wrap to multiple lines");
82          Assertions.assertTrue(lines.stream().noneMatch(line -> line.contains("\u00A0")),
83                  "Non-breaking spaces should be normalized");
84          Assertions.assertEquals(2, invokeStatic("getIndentLevel", new Class<?>[] { String.class }, "\t  \tvalue"));
85      }
86  
87      @Test
88      void testWriteParameterIncludesMetadata() throws Exception {
89          Object mojo = newHelpMojo();
90          setField(mojo, "lineLength", 40);
91          setField(mojo, "indentSize", 2);
92  
93          Document doc = parse(
94                  "<mojo>" + "<configuration><sample default-value=\"42\">${sample.property}</sample></configuration>"
95                          + "<parameter><name>sample</name><description>parameter description</description>"
96                          + "<required>true</required><deprecated>use replacement</deprecated></parameter>" + "</mojo>");
97  
98          StringBuilder sb = new StringBuilder();
99          Node parameter = doc.getElementsByTagName("parameter").item(0);
100         Node configuration = doc.getElementsByTagName("configuration").item(0);
101         invokeInstance(mojo, "writeParameter", new Class<?>[] { StringBuilder.class, Node.class, Node.class }, sb,
102                 parameter, configuration);
103 
104         String output = sb.toString();
105         Assertions.assertTrue(output.contains("sample (Default: 42)"));
106         Assertions.assertTrue(output.contains("Deprecated. use replacement"));
107         Assertions.assertTrue(output.contains("Required: Yes"));
108         Assertions.assertTrue(output.contains("User property: sample.property"));
109         Assertions.assertTrue(output.contains("parameter description"));
110     }
111 
112     private static Object newHelpMojo() throws ReflectiveOperationException {
113         return helpMojoClass().getDeclaredConstructor().newInstance();
114     }
115 
116     private static Class<?> helpMojoClass() throws ClassNotFoundException {
117         return Class.forName(HELP_MOJO_CLASS);
118     }
119 
120     private static void setField(Object target, String fieldName, Object value) throws ReflectiveOperationException {
121         Field field = target.getClass().getDeclaredField(fieldName);
122         field.setAccessible(true);
123         field.set(target, value);
124     }
125 
126     private static Object getField(Object target, String fieldName) throws ReflectiveOperationException {
127         Field field = target.getClass().getDeclaredField(fieldName);
128         field.setAccessible(true);
129         return field.get(target);
130     }
131 
132     private static Object invokeStatic(String methodName, Class<?>[] parameterTypes, Object... args)
133             throws ReflectiveOperationException {
134         Method method = helpMojoClass().getDeclaredMethod(methodName, parameterTypes);
135         method.setAccessible(true);
136         try {
137             return method.invoke(null, args);
138         } catch (InvocationTargetException e) {
139             throw new ReflectiveOperationException(e.getCause());
140         }
141     }
142 
143     private static Object invokeInstance(Object target, String methodName, Class<?>[] parameterTypes, Object... args)
144             throws ReflectiveOperationException {
145         Method method = target.getClass().getDeclaredMethod(methodName, parameterTypes);
146         method.setAccessible(true);
147         try {
148             return method.invoke(target, args);
149         } catch (InvocationTargetException e) {
150             throw new ReflectiveOperationException(e.getCause());
151         }
152     }
153 
154     private static Document parse(String xml) throws Exception {
155         return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
156     }
157 }