View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.html;
16  
17  import java.lang.reflect.Method;
18  import java.util.ArrayList;
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.Locale;
22  
23  import org.htmlunit.BrowserVersion;
24  import org.htmlunit.MockWebConnection;
25  import org.htmlunit.WebClient;
26  import org.htmlunit.WebTestCase;
27  import org.htmlunit.html.parser.HTMLParser;
28  
29  import junit.framework.Test;
30  import junit.framework.TestCase;
31  import junit.framework.TestSuite;
32  
33  /**
34   * <p>Tests for all the generated attribute accessors. This test case will
35   * dynamically generate tests for all the various attributes. The code
36   * is fairly complicated but doing it this way is much easier than writing
37   * individual tests for all the attributes.</p>
38   *
39   * <p>With the new custom DOM, this test has somewhat lost its significance.
40   * We simply set and get the attributes and compare the results.</p>
41   *
42   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
43   * @author Christian Sell
44   * @author Marc Guillemot
45   * @author Ahmed Ashour
46   * @author Ronald Brill
47   * @author Frank Danek
48   */
49  public class AttributesTest extends TestCase {
50  
51      private final Class<?> classUnderTest_;
52      private final Method method_;
53      private final String attributeName_;
54  
55      private static final List<String> EXCLUDED_METHODS = new ArrayList<>();
56      static {
57          EXCLUDED_METHODS.add("getHtmlElementsByAttribute");
58          EXCLUDED_METHODS.add("getOneHtmlElementByAttribute");
59          EXCLUDED_METHODS.add("getAttribute");
60          EXCLUDED_METHODS.add("getElementsByAttribute");
61      }
62  
63      /**
64       * Returns a test suite containing a separate test for each attribute on each element.
65       *
66       * @return the test suite
67       * @throws Exception if the tests cannot be created
68       */
69      public static Test suite() throws Exception {
70          final TestSuite suite = new TestSuite();
71          final String[] classesToTest = {
72              "HtmlAbbreviated", "HtmlAcronym",
73              "HtmlAnchor", "HtmlAddress", "HtmlArea",
74              "HtmlArticle", "HtmlAside", "HtmlAudio",
75              "HtmlBackgroundSound", "HtmlBase", "HtmlBaseFont",
76              "HtmlBidirectionalIsolation",
77              "HtmlBidirectionalOverride", "HtmlBig",
78              "HtmlBlockQuote", "HtmlBody", "HtmlBold",
79              "HtmlBreak", "HtmlButton", "HtmlCanvas", "HtmlCaption",
80              "HtmlCenter", "HtmlCitation", "HtmlCode", "DomComment",
81              "HtmlData", "HtmlDataList",
82              "HtmlDefinition", "HtmlDefinitionDescription",
83              "HtmlDeletedText", "HtmlDetails", "HtmlDialog", "HtmlDirectory",
84              "HtmlDivision", "HtmlDefinitionList",
85              "HtmlDefinitionTerm", "HtmlEmbed",
86              "HtmlEmphasis",
87              "HtmlFieldSet", "HtmlFigureCaption", "HtmlFigure",
88              "HtmlFont", "HtmlForm", "HtmlFooter",
89              "HtmlFrame", "HtmlFrameSet",
90              "HtmlHead", "HtmlHeader",
91              "HtmlHeading1", "HtmlHeading2", "HtmlHeading3",
92              "HtmlHeading4", "HtmlHeading5", "HtmlHeading6",
93              "HtmlHorizontalRule", "HtmlHtml", "HtmlInlineFrame",
94              "HtmlInlineQuotation",
95              "HtmlImage", "HtmlImage", "HtmlInsertedText",
96              "HtmlItalic", "HtmlKeyboard", "HtmlLabel", "HtmlLayer",
97              "HtmlLegend", "HtmlListing", "HtmlListItem",
98              "HtmlLink",
99              "HtmlMap", "HtmlMain", "HtmlMark", "HtmlMarquee",
100             "HtmlMenu", "HtmlMeta", "HtmlMeter",
101             "HtmlNav",
102             "HtmlNoBreak", "HtmlNoEmbed", "HtmlNoFrames", "HtmlNoLayer",
103             "HtmlNoScript", "HtmlObject", "HtmlOrderedList",
104             "HtmlOptionGroup", "HtmlOption", "HtmlOutput",
105             "HtmlParagraph",
106             "HtmlParameter", "HtmlPicture", "HtmlPlainText", "HtmlPreformattedText",
107             "HtmlProgress",
108             "HtmlRb", "HtmlRp", "HtmlRt", "HtmlRtc", "HtmlRuby",
109             "HtmlS", "HtmlSample",
110             "HtmlScript", "HtmlSection", "HtmlSelect", "HtmlSlot", "HtmlSmall",
111             "HtmlSource", "HtmlSpan",
112             "HtmlStrike", "HtmlStrong", "HtmlStyle",
113             "HtmlSubscript", "HtmlSummary", "HtmlSuperscript",
114             "HtmlSvg",
115             "HtmlTable", "HtmlTableColumn", "HtmlTableColumnGroup",
116             "HtmlTableBody", "HtmlTableDataCell", "HtmlTableHeaderCell",
117             "HtmlTableRow", "HtmlTextArea", "HtmlTableFooter",
118             "HtmlTableHeader", "HtmlTeletype", "HtmlTemplate", "HtmlTrack",
119             "HtmlTime", "HtmlTitle",
120             "HtmlUnderlined", "HtmlUnorderedList",
121             "HtmlVariable", "HtmlVideo",
122             "HtmlWordBreak", "HtmlExample"
123         };
124 
125         final HashSet<String> supportedTags = new HashSet<>(DefaultElementFactory.SUPPORTED_TAGS_);
126 
127         for (final String testClass : classesToTest) {
128             final Class<?> clazz = Class.forName("org.htmlunit.html." + testClass);
129             addTestsForClass(clazz, suite);
130 
131             String tag;
132             if (DomComment.class == clazz) {
133                 tag = "comment";
134             }
135             else {
136                 tag = (String) clazz.getField("TAG_NAME").get(null);
137             }
138             supportedTags.remove(tag);
139             try {
140                 tag = (String) clazz.getField("TAG_NAME2").get(null);
141                 supportedTags.remove(tag);
142             }
143             catch (final NoSuchFieldException ignored) {
144                 // ignore
145             }
146         }
147 
148         supportedTags.remove("input");
149 
150         if (!supportedTags.isEmpty()) {
151             throw new RuntimeException("Missing tag class(es) " + supportedTags);
152         }
153         return suite;
154     }
155 
156     /**
157      * Adds all the tests for a given class.
158      *
159      * @param clazz the class to create tests for
160      * @param page the page that will be passed into the constructor of the objects to be tested
161      * @param suite the suite that all the tests will be placed inside
162      * @throws Exception if the tests cannot be created
163      */
164     private static void addTestsForClass(final Class<?> clazz, final TestSuite suite)
165         throws Exception {
166 
167         final Method[] methods = clazz.getMethods();
168         for (final Method method : methods) {
169             final String methodName = method.getName();
170             if (methodName.startsWith("get")
171                 && methodName.endsWith("Attribute")
172                 && !EXCLUDED_METHODS.contains(methodName)) {
173 
174                 String attributeName = methodName.substring(3, methodName.length() - 9).toLowerCase(Locale.ROOT);
175                 if ("xmllang".equals(attributeName)) {
176                     attributeName = "xml:lang";
177                 }
178                 else if ("columns".equals(attributeName)) {
179                     attributeName = "cols";
180                 }
181                 else if ("columnspan".equals(attributeName)) {
182                     attributeName = "colspan";
183                 }
184                 else if ("textdirection".equals(attributeName)) {
185                     attributeName = "dir";
186                 }
187                 else if ("httpequiv".equals(attributeName)) {
188                     attributeName = "http-equiv";
189                 }
190                 else if ("acceptcharset".equals(attributeName)) {
191                     attributeName = "accept-charset";
192                 }
193                 else if ("htmlfor".equals(attributeName)) {
194                     attributeName = "for";
195                 }
196                 suite.addTest(new AttributesTest(attributeName, clazz, method));
197             }
198         }
199     }
200 
201     /**
202      * Creates an instance of the test. This will test one specific attribute
203      * on one specific class.
204      * @param attributeName the name of the attribute to test
205      * @param classUnderTest the class containing the attribute
206      * @param method the "getter" method for the specified attribute
207      */
208     public AttributesTest(
209             final String attributeName,
210             final Class<?> classUnderTest,
211             final Method method) {
212 
213         super(createTestName(classUnderTest, method));
214         classUnderTest_ = classUnderTest;
215         method_ = method;
216         attributeName_ = attributeName;
217     }
218 
219     /**
220      * Creates a name for this particular test that reflects the attribute being tested.
221      * @param clazz the class containing the attribute
222      * @param method the getter method for the attribute
223      * @return the test name
224      */
225     private static String createTestName(final Class<?> clazz, final Method method) {
226         String className = clazz.getName();
227         final int index = className.lastIndexOf('.');
228         className = className.substring(index + 1);
229 
230         return "testAttributes_" + className + '_' + method.getName();
231     }
232 
233     /**
234      * Runs the actual test.
235      * @throws Exception if the test fails
236      */
237     @Override
238     protected void runTest() throws Exception {
239         try (WebClient webClient = new WebClient(BrowserVersion.BEST_SUPPORTED)) {
240             final MockWebConnection connection = new MockWebConnection();
241             connection.setDefaultResponse("<html><head><title>foo</title></head><body></body></html>");
242             webClient.setWebConnection(connection);
243             final HtmlPage page = webClient.getPage(WebTestCase.URL_FIRST);
244 
245             final String value = "value";
246 
247             final DomElement objectToTest = getNewInstanceForClassUnderTest(page);
248             objectToTest.setAttribute(attributeName_, value);
249 
250             final Object[] noObjects = new Object[0];
251             final Object result = method_.invoke(objectToTest, noObjects);
252             assertSame(value, result);
253         }
254     }
255 
256     /**
257      * Creates a new instance of the class being tested.
258      * @return the new instance
259      * @throws Exception if the new object cannot be created
260      */
261     private DomElement getNewInstanceForClassUnderTest(final HtmlPage page) throws Exception {
262         final HTMLParser htmlParser = page.getWebClient().getPageCreator().getHtmlParser();
263         final DomElement newInstance;
264         if (classUnderTest_ == HtmlTableRow.class) {
265             newInstance = htmlParser.getFactory(HtmlTableRow.TAG_NAME)
266                     .createElement(page, HtmlTableRow.TAG_NAME, null);
267         }
268         else if (classUnderTest_ == HtmlTableHeaderCell.class) {
269             newInstance = htmlParser.getFactory(HtmlTableHeaderCell.TAG_NAME)
270                     .createElement(page, HtmlTableHeaderCell.TAG_NAME, null);
271         }
272         else if (classUnderTest_ == HtmlTableDataCell.class) {
273             newInstance = htmlParser.getFactory(HtmlTableDataCell.TAG_NAME)
274                     .createElement(page, HtmlTableDataCell.TAG_NAME, null);
275         }
276         else {
277             final String tagName = (String) classUnderTest_.getField("TAG_NAME").get(null);
278             newInstance = htmlParser.getFactory(tagName).createElement(page, tagName, null);
279         }
280 
281         return newInstance;
282     }
283 }