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.parser;
16  
17  import java.net.URL;
18  import java.util.ArrayList;
19  import java.util.List;
20  
21  import org.apache.commons.lang3.builder.EqualsBuilder;
22  import org.apache.commons.lang3.builder.HashCodeBuilder;
23  import org.htmlunit.MockWebConnection;
24  import org.htmlunit.SimpleWebTestCase;
25  import org.htmlunit.WebClient;
26  import org.htmlunit.html.HtmlPage;
27  import org.junit.jupiter.api.Test;
28  
29  /**
30   * Test class for {@link HTMLParserListener}.
31   *
32   * We probably don't need to check the details of the messages generated by the
33   * parser but just that we catch and "transmit" them.
34   *
35   * @author Marc Guillemot
36   * @author Ronald Brill
37   */
38  public class HTMLParserListenerTest extends SimpleWebTestCase {
39      static class MessageInfo {
40          private boolean error_; // versus warning
41          private String message_;
42          private URL url_;
43          private String html_;
44          private int line_;
45          private int column_;
46  
47          /**
48           * Utility class to hold data.
49           * @param error the error
50           * @param message the message
51           * @param url the URL
52           * @param line the line number
53           * @param column the column number
54           * @param key Ignored value
55           */
56          MessageInfo(final boolean error, final String message, final URL url, final String html,
57                  final int line, final int column, final String key) {
58              error_ = error;
59              message_ = message;
60              url_ = url;
61              html_ = html;
62              line_ = line;
63              column_ = column;
64              // ignore key
65          }
66  
67          /**
68           * @see Object#toString()
69           */
70          @Override
71          public String toString() {
72              return message_ + " (" + url_ + " " + line_ + ":" + column_ + ")";
73          }
74  
75          /**
76           * Compares according to error, message, URL and line.
77           * @see Object#equals(Object)
78           */
79          @Override
80          public boolean equals(final Object obj) {
81              if (!(obj instanceof MessageInfo)) {
82                  return false;
83              }
84              final MessageInfo other = (MessageInfo) obj;
85              final EqualsBuilder builder = new EqualsBuilder();
86              builder.append(error_, other.error_);
87              builder.append(message_, other.message_);
88              builder.append(url_.toExternalForm(), other.url_.toExternalForm());
89              builder.append(html_, other.html_);
90              builder.append(line_, other.line_);
91              builder.append(column_, other.column_);
92              return builder.isEquals();
93          }
94  
95          /**
96           * @see Object#hashCode()
97           */
98          @Override
99          public int hashCode() {
100             final HashCodeBuilder builder = new HashCodeBuilder();
101             builder.append(error_);
102             builder.append(message_);
103             builder.append(url_);
104             builder.append(html_);
105             builder.append(line_);
106             builder.append(column_);
107             return builder.hashCode();
108         }
109     }
110 
111     /**
112      * This is the right test as we had before HtmlUnit-2.6 but due
113      * to an (accepted - at least in a first time -) regression in
114      * NekoHTML, it doesn't work anymore.
115      * testSimple_withWrongLineCol ensures that no other regression occurs here.
116      * @exception Exception If the test fails
117      */
118     @Test
119     public void simple() throws Exception {
120         testSimple(5, 8);
121     }
122 
123     /**
124      * Currently, NekoHtml doesn't deliver right information about the line
125      * for the warning. Let this test run with wrong expectation
126      * on line and col to avoid that other regression occur.
127      * @exception Exception If the test fails
128      */
129     @Test
130     public void simple_withWrongLineCol() throws Exception {
131         testSimple(5, 8);
132     }
133 
134     private void testSimple(final int line, final int col) throws Exception {
135         final String htmlContent =
136                 "<html>\n"
137                 + "<body>\n"
138                 + "<p>foo\n"
139                 + "</body>"
140                 + "\n</html>";
141 
142         final WebClient webClient = getWebClient();
143         assertNull(webClient.getHTMLParserListener());
144 
145         final List<MessageInfo> messages = new ArrayList<>();
146         final HTMLParserListener collecter = new HTMLParserListener() {
147 
148             @Override
149             public void error(final String message, final URL url, final String html,
150                     final int errorLine, final int column, final String key) {
151                 messages.add(new MessageInfo(true, message, url, html, errorLine, column, key));
152             }
153 
154             @Override
155             public void warning(final String message, final URL url, final String html,
156                     final int warningLine, final int column, final String key) {
157                 messages.add(new MessageInfo(false, message, url, html, warningLine, column, key));
158             }
159         };
160         webClient.setHTMLParserListener(collecter);
161 
162         final MockWebConnection webConnection = new MockWebConnection();
163         webConnection.setDefaultResponse(htmlContent);
164         webClient.setWebConnection(webConnection);
165 
166         final HtmlPage page = webClient.getPage(URL_FIRST);
167         assertEquals("foo", page.asNormalizedText());
168 
169         // ignore column and key
170         final MessageInfo expectedError = new MessageInfo(false,
171                 "End element <body> automatically closes element <p>.",
172                 URL_FIRST, null, line, col, null);
173 
174         assertEquals(1, messages.size());
175         assertEquals(expectedError, messages.get(0));
176     }
177 
178     /**
179      * Test parsing of a fragment.
180      * @exception Exception If the test fails
181      */
182     @Test
183     public void parseFragment() throws Exception {
184         final String htmlContent = "<html><head><title>foo</title>\n"
185                 + "<script>\n"
186                 + "function test() {\n"
187                 + "  var oNode = document.getElementById('middle');\n"
188                 + "  oNode.insertAdjacentHTML('afterEnd', '<div><span>before end</div>');\n"
189                 + "}\n"
190                 + "</script>\n"
191                 + "</head>\n"
192                 + "<body onload='test()'>\n"
193                 + "  <span id='middle' style='color: #ff0000'>\n"
194                 + "    inside\n"
195                 + "  </span>\n"
196                 + "</body></html>";
197 
198         final WebClient webClient = getWebClient();
199         assertNull(webClient.getHTMLParserListener());
200 
201         final List<MessageInfo> messages = new ArrayList<>();
202         final HTMLParserListener collecter = new HTMLParserListener() {
203 
204             @Override
205             public void error(final String message, final URL url, final String html,
206                     final int line, final int column, final String key) {
207                 messages.add(new MessageInfo(true, message, url, html, line, column, key));
208             }
209 
210             @Override
211             public void warning(final String message, final URL url, final String html,
212                     final int line, final int column, final String key) {
213                 messages.add(new MessageInfo(false, message, url, html, line, column, key));
214             }
215         };
216         webClient.setHTMLParserListener(collecter);
217 
218         final MockWebConnection webConnection = new MockWebConnection();
219         webConnection.setDefaultResponse(htmlContent);
220         webClient.setWebConnection(webConnection);
221 
222         final HtmlPage page = webClient.getPage(URL_FIRST);
223         assertEquals("foo", page.getTitleText());
224 
225         // ignore key
226         final MessageInfo expectedError = new MessageInfo(false,
227                 "End element <div> automatically closes element <span>.",
228                 URL_FIRST, "<div><span>before end</div>", 1, 28, null);
229 
230         assertEquals(1, messages.size());
231         assertEquals(expectedError, messages.get(0));
232     }
233 }