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 org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.annotation.Alerts;
19  import org.htmlunit.junit.annotation.BuggyWebDriver;
20  import org.htmlunit.junit.annotation.HtmlUnitNYI;
21  import org.junit.jupiter.api.Test;
22  import org.openqa.selenium.By;
23  import org.openqa.selenium.InvalidElementStateException;
24  import org.openqa.selenium.WebDriver;
25  import org.openqa.selenium.WebElement;
26  import org.openqa.selenium.htmlunit.HtmlUnitDriver;
27  
28  /**
29   * Tests for {@link HtmlTimeInput}.
30   *
31   * @author Ronald Brill
32   * @author Anton Demydenko
33   */
34  public class HtmlTimeInputTest extends WebDriverTestCase {
35  
36      /**
37       * @throws Exception if the test fails
38       */
39      @Test
40      @Alerts({"--null", "--null", "--null"})
41      public void defaultValues() throws Exception {
42          final String html = DOCTYPE_HTML
43              + "<html><head>\n"
44              + "<script>\n"
45              + LOG_TITLE_FUNCTION
46              + "  function test() {\n"
47              + "    var input = document.getElementById('text1');\n"
48              + "    log(input.value + '-' + input.defaultValue + '-' + input.getAttribute('value'));\n"
49  
50              + "    try {\n"
51              + "      input = document.createElement('input');\n"
52              + "      input.type = 'time';\n"
53              + "      log(input.value + '-' + input.defaultValue + '-' + input.getAttribute('value'));\n"
54              + "    } catch(e)  { logEx(e); }\n"
55  
56              + "    var builder = document.createElement('div');\n"
57              + "    builder.innerHTML = '<input type=\"time\">';\n"
58              + "    input = builder.firstChild;\n"
59              + "    log(input.value + '-' + input.defaultValue + '-' + input.getAttribute('value'));\n"
60              + "  }\n"
61              + "</script>\n"
62              + "</head><body onload='test()'>\n"
63              + "<form>\n"
64              + "  <input type='time' id='text1'>\n"
65              + "</form>\n"
66              + "</body></html>";
67  
68          loadPageVerifyTitle2(html);
69      }
70  
71      /**
72       * @throws Exception if the test fails
73       */
74      @Test
75      @Alerts({"--null", "--null", "--null"})
76      public void defaultValuesAfterClone() throws Exception {
77          final String html = DOCTYPE_HTML
78              + "<html><head>\n"
79              + "<script>\n"
80              + LOG_TITLE_FUNCTION
81              + "  function test() {\n"
82              + "    var input = document.getElementById('text1');\n"
83              + "    input = input.cloneNode(false);\n"
84              + "    log(input.value + '-' + input.defaultValue + '-' + input.getAttribute('value'));\n"
85  
86              + "    try {\n"
87              + "      input = document.createElement('input');\n"
88              + "      input.type = 'time';\n"
89              + "      input = input.cloneNode(false);\n"
90              + "      log(input.value + '-' + input.defaultValue + '-' + input.getAttribute('value'));\n"
91              + "    } catch(e)  { logEx(e); }\n"
92  
93              + "    var builder = document.createElement('div');\n"
94              + "    builder.innerHTML = '<input type=\"time\">';\n"
95              + "    input = builder.firstChild;\n"
96              + "    input = input.cloneNode(false);\n"
97              + "    log(input.value + '-' + input.defaultValue + '-' + input.getAttribute('value'));\n"
98              + "  }\n"
99              + "</script>\n"
100             + "</head><body onload='test()'>\n"
101             + "<form>\n"
102             + "  <input type='time' id='text1'>\n"
103             + "</form>\n"
104             + "</body></html>";
105 
106         loadPageVerifyTitle2(html);
107     }
108 
109     /**
110      * @throws Exception if the test fails
111      */
112     @Test
113     @Alerts(DEFAULT = {"", "20:04"},
114             FF = {"08:04", "20:04"},
115             FF_ESR = {"08:04", "20:04"})
116     @BuggyWebDriver(FF = {"08:04", ""},
117             FF_ESR = {"08:04", ""})
118     @HtmlUnitNYI(CHROME = {"08:04", "20:04"},
119             EDGE = {"08:04", "20:04"})
120     public void type() throws Exception {
121         final String htmlContent = DOCTYPE_HTML
122             + "<html><head></head><body>\n"
123             + "<form id='form1'>\n"
124             + "  <input type='time' id='foo'>\n"
125             + "</form></body></html>";
126 
127         final WebDriver driver = loadPage2(htmlContent);
128 
129         final WebElement input = driver.findElement(By.id("foo"));
130 
131         input.sendKeys("0804");
132         assertNull(input.getDomAttribute("value"));
133         assertEquals(getExpectedAlerts()[0], input.getDomProperty("value"));
134 
135         input.sendKeys("PM");
136         assertNull(input.getDomAttribute("value"));
137         assertEquals(getExpectedAlerts()[1], input.getDomProperty("value"));
138     }
139 
140     /**
141      * @throws Exception if the test fails
142      */
143     @Test
144     @Alerts("ex: ")
145     public void typeWhileDisabled() throws Exception {
146         final String html = DOCTYPE_HTML + "<html><body><input type='time' id='p' disabled='disabled'/></body></html>";
147         final WebDriver driver = loadPage2(html);
148         final WebElement p = driver.findElement(By.id("p"));
149         try {
150             p.sendKeys("804PM");
151             assertEquals(getExpectedAlerts()[0], "no ex: " + p.getDomProperty("value"));
152             return;
153         }
154         catch (final InvalidElementStateException e) {
155             // as expected
156         }
157         assertEquals(getExpectedAlerts()[0], "ex: " + p.getDomProperty("value"));
158     }
159 
160     /**
161      * @throws Exception if the test fails
162      */
163     @Test
164     @Alerts({"null", "null"})
165     public void typeDoesNotChangeValueAttribute() throws Exception {
166         final String html = DOCTYPE_HTML
167                 + "<html>\n"
168                 + "<head></head>\n"
169                 + "<body>\n"
170                 + "  <input type='time' id='t'/>\n"
171                 + "  <button id='check' onclick='alert(document.getElementById(\"t\").getAttribute(\"value\"));'>"
172                         + "DoIt</button>\n"
173                 + "</body></html>";
174 
175         final WebDriver driver = loadPage2(html);
176         final WebElement t = driver.findElement(By.id("t"));
177 
178         final WebElement check = driver.findElement(By.id("check"));
179         check.click();
180         verifyAlerts(driver, getExpectedAlerts()[0]);
181 
182         t.sendKeys("0804PM");
183         check.click();
184         verifyAlerts(driver, getExpectedAlerts()[1]);
185     }
186 
187     /**
188      * @throws Exception if the test fails
189      */
190     @Test
191     @Alerts({"20:04", "20:04"})
192     public void typeDoesNotChangeValueAttributeWithInitialValue() throws Exception {
193         final String html = DOCTYPE_HTML
194                 + "<html>\n"
195                 + "<head></head>\n"
196                 + "<body>\n"
197                 + "  <input type='time' id='t' value='20:04'/>\n"
198                 + "  <button id='check' onclick='alert(document.getElementById(\"t\").getAttribute(\"value\"));'>"
199                         + "DoIt</button>\n"
200                 + "</body></html>";
201 
202         final WebDriver driver = loadPage2(html);
203         final WebElement t = driver.findElement(By.id("t"));
204 
205         final WebElement check = driver.findElement(By.id("check"));
206         check.click();
207         verifyAlerts(driver, getExpectedAlerts()[0]);
208 
209         t.sendKeys("0905AM");
210         check.click();
211         verifyAlerts(driver, getExpectedAlerts()[1]);
212     }
213 
214     /**
215      * Verifies getVisibleText().
216      * @throws Exception if the test fails
217      */
218     @Test
219     @Alerts("")
220     public void getVisibleText() throws Exception {
221         final String htmlContent = DOCTYPE_HTML
222             + "<html>\n"
223             + "<head></head>\n"
224             + "<body>\n"
225             + "<form id='form1'>\n"
226             + "  <input type='time' name='tester' id='tester' value='11:55' min='09:00' max='18:00'>\n"
227             + "</form>\n"
228             + "</body></html>";
229 
230         final WebDriver driver = loadPage2(htmlContent);
231         final String text = driver.findElement(By.id("tester")).getText();
232         assertEquals(getExpectedAlerts()[0], text);
233 
234         if (driver instanceof HtmlUnitDriver) {
235             final HtmlPage page = (HtmlPage) getEnclosedPage();
236             assertEquals(getExpectedAlerts()[0], page.getBody().getVisibleText());
237         }
238     }
239 
240     /**
241      * Verifies clear().
242      * @throws Exception if the test fails
243      */
244     @Test
245     @Alerts({"11:55", ""})
246     public void clearInput() throws Exception {
247         final String htmlContent = DOCTYPE_HTML
248                 + "<html>\n"
249                 + "<head></head>\n"
250                 + "<body>\n"
251                 + "<form id='form1'>\n"
252                 + "  <input type='time' name='tester' id='tester' value='11:55'>\n"
253                 + "</form>\n"
254                 + "</body></html>";
255 
256         final WebDriver driver = loadPage2(htmlContent);
257         final WebElement element = driver.findElement(By.id("tester"));
258 
259         assertEquals(getExpectedAlerts()[0], element.getDomAttribute("value"));
260         assertEquals(getExpectedAlerts()[0], element.getDomProperty("value"));
261 
262         element.clear();
263         assertEquals(getExpectedAlerts()[0], element.getDomAttribute("value"));
264         assertEquals(getExpectedAlerts()[1], element.getDomProperty("value"));
265     }
266 
267     /**
268      * @throws Exception if the test fails
269      */
270     @Test
271     @Alerts("--")
272     public void minMaxStep() throws Exception {
273         final String html = DOCTYPE_HTML
274             + "<html>\n"
275             + "<head>\n"
276             + "<script>\n"
277             + LOG_TITLE_FUNCTION
278             + "  function test() {\n"
279             + "    var input = document.getElementById('tester');\n"
280             + "    log(input.min + '-' + input.max + '-' + input.step);\n"
281             + "  }\n"
282             + "</script>\n"
283             + "</head>\n"
284             + "<body onload='test()'>\n"
285             + "<form>\n"
286             + "  <input type='time' id='tester'>\n"
287             + "</form>\n"
288             + "</body>\n"
289             + "</html>";
290 
291         loadPageVerifyTitle2(html);
292     }
293 
294     /**
295      * @throws Exception if an error occurs
296      */
297     @Test
298     @Alerts("true-false-true-true-true-true")
299     public void minValidation() throws Exception {
300         final String html = DOCTYPE_HTML
301             + "<html>\n"
302             + "<head>\n"
303             + "<script>\n"
304             + LOG_TITLE_FUNCTION
305             + "  function test() {\n"
306             + "    log(document.getElementById('id1').checkValidity() + '-'\n"
307             + "         + document.getElementById('id2').checkValidity() + '-'\n"
308             + "         + document.getElementById('id3').checkValidity() + '-'\n"
309             + "         + document.getElementById('id4').checkValidity() + '-'\n"
310             + "         + document.getElementById('id5').checkValidity() + '-'\n"
311             + "         + document.getElementById('id6').checkValidity());\n"
312             + "  }\n"
313             + "</script>\n"
314             + "</head>\n"
315             + "<body onload='test()'>\n"
316             + "  <input type='time' id='id1' min='09:00'>\n"
317             + "  <input type='time' id='id2' min='09:00' value='08:00'>\n"
318             + "  <input type='time' id='id3' min='09:00' value='09:00'>\n"
319             + "  <input type='time' id='id4' min='09:00' value='10:00'>\n"
320             + "  <input type='time' id='id5' value='09:00'>\n"
321             + "  <input type='time' id='id6' min='foo' value='09:00'>\n"
322             + "</body>\n"
323             + "</html>";
324 
325         loadPageVerifyTitle2(html);
326     }
327 
328     /**
329      * @throws Exception if an error occurs
330      */
331     @Test
332     @Alerts("true-true-true-false-true-true")
333     public void maxValidation() throws Exception {
334         final String html = DOCTYPE_HTML
335             + "<html>\n"
336             + "<head>\n"
337             + "<script>\n"
338             + LOG_TITLE_FUNCTION
339             + "  function test() {\n"
340             + "    log(document.getElementById('id1').checkValidity() + '-'\n"
341             + "         + document.getElementById('id2').checkValidity() + '-'\n"
342             + "         + document.getElementById('id3').checkValidity() + '-'\n"
343             + "         + document.getElementById('id4').checkValidity() + '-'\n"
344             + "         + document.getElementById('id5').checkValidity() + '-'\n"
345             + "         + document.getElementById('id6').checkValidity());\n"
346             + "  }\n"
347             + "</script>\n"
348             + "</head>\n"
349             + "<body onload='test()'>\n"
350             + "  <input type='time' id='id1' max='09:00'>\n"
351             + "  <input type='time' id='id2' max='09:00' value='08:00'>\n"
352             + "  <input type='time' id='id3' max='09:00' value='09:00'>\n"
353             + "  <input type='time' id='id4' max='09:00' value='10:00'>\n"
354             + "  <input type='time' id='id5' value='09:00'>\n"
355             + "  <input type='time' id='id6' max='foo' value='09:00'>\n"
356             + "</body>\n"
357             + "</html>";
358 
359         loadPageVerifyTitle2(html);
360     }
361 
362     /**
363      * @throws Exception if an error occurs
364      */
365     @Test
366     @Alerts({"true", "false", "true", "false", "true"})
367     public void willValidate() throws Exception {
368         final String html = DOCTYPE_HTML
369                 + "<html><head>\n"
370                 + "  <script>\n"
371                 + LOG_TITLE_FUNCTION
372                 + "    function test() {\n"
373                 + "      log(document.getElementById('o1').willValidate);\n"
374                 + "      log(document.getElementById('o2').willValidate);\n"
375                 + "      log(document.getElementById('o3').willValidate);\n"
376                 + "      log(document.getElementById('o4').willValidate);\n"
377                 + "      log(document.getElementById('o5').willValidate);\n"
378                 + "    }\n"
379                 + "  </script>\n"
380                 + "</head>\n"
381                 + "<body onload='test()'>\n"
382                 + "  <form>\n"
383                 + "    <input type='time' id='o1'>\n"
384                 + "    <input type='time' id='o2' disabled>\n"
385                 + "    <input type='time' id='o3' hidden>\n"
386                 + "    <input type='time' id='o4' readonly>\n"
387                 + "    <input type='time' id='o5' style='display: none'>\n"
388                 + "  </form>\n"
389                 + "</body></html>";
390 
391         loadPageVerifyTitle2(html);
392     }
393 
394     /**
395      * @throws Exception if an error occurs
396      */
397     @Test
398     @Alerts({"true",
399              "false-false-false-false-false-false-false-false-false-true-false",
400              "true"})
401     public void validationEmpty() throws Exception {
402         validation("<input type='time' id='e1'>\n", "");
403     }
404 
405     /**
406      * @throws Exception if an error occurs
407      */
408     @Test
409     @Alerts({"false",
410              "false-true-false-false-false-false-false-false-false-false-false",
411              "true"})
412     public void validationCustomValidity() throws Exception {
413         validation("<input type='time' id='e1'>\n", "elem.setCustomValidity('Invalid');");
414     }
415 
416     /**
417      * @throws Exception if an error occurs
418      */
419     @Test
420     @Alerts({"false",
421              "false-true-false-false-false-false-false-false-false-false-false",
422              "true"})
423     public void validationBlankCustomValidity() throws Exception {
424         validation("<input type='time' id='e1'>\n", "elem.setCustomValidity(' ');\n");
425     }
426 
427     /**
428      * @throws Exception if an error occurs
429      */
430     @Test
431     @Alerts({"true",
432              "false-false-false-false-false-false-false-false-false-true-false",
433              "true"})
434     public void validationResetCustomValidity() throws Exception {
435         validation("<input type='time' id='e1'>\n",
436                 "elem.setCustomValidity('Invalid');elem.setCustomValidity('');");
437     }
438 
439     /**
440      * @throws Exception if an error occurs
441      */
442     @Test
443     @Alerts({"false",
444              "false-false-false-false-false-false-false-false-false-false-true",
445              "true"})
446     public void validationRequired() throws Exception {
447         validation("<input type='time' id='e1' required>\n", "");
448     }
449 
450     /**
451      * @throws Exception if an error occurs
452      */
453     @Test
454     @Alerts({"true",
455              "false-false-false-false-false-false-false-false-false-true-false",
456              "true"})
457     public void validationRequiredValueSet() throws Exception {
458         validation("<input type='time' id='e1' required>\n", "elem.value='10:00';");
459     }
460 
461     private void validation(final String htmlPart, final String jsPart) throws Exception {
462         final String html = DOCTYPE_HTML
463                 + "<html><head>\n"
464                 + "  <script>\n"
465                 + LOG_TITLE_FUNCTION
466                 + "    function logValidityState(s) {\n"
467                 + "      log(s.badInput"
468                         + "+ '-' + s.customError"
469                         + "+ '-' + s.patternMismatch"
470                         + "+ '-' + s.rangeOverflow"
471                         + "+ '-' + s.rangeUnderflow"
472                         + "+ '-' + s.stepMismatch"
473                         + "+ '-' + s.tooLong"
474                         + "+ '-' + s.tooShort"
475                         + " + '-' + s.typeMismatch"
476                         + " + '-' + s.valid"
477                         + " + '-' + s.valueMissing);\n"
478                 + "    }\n"
479                 + "    function test() {\n"
480                 + "      var elem = document.getElementById('e1');\n"
481                 + jsPart
482                 + "      log(elem.checkValidity());\n"
483                 + "      logValidityState(elem.validity);\n"
484                 + "      log(elem.willValidate);\n"
485                 + "    }\n"
486                 + "  </script>\n"
487                 + "</head>\n"
488                 + "<body onload='test()'>\n"
489                 + "  <form>\n"
490                 + htmlPart
491                 + "  </form>\n"
492                 + "</body></html>";
493 
494         loadPageVerifyTitle2(html);
495     }
496 }