Skip to content

Commit 7665b1c

Browse files
committed
add test for checkValidity()/reportValidity() and css match for HtmlInput and small fixes
1 parent 872cc84 commit 7665b1c

4 files changed

Lines changed: 639 additions & 2 deletions

File tree

src/changes/changes.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</action>
2424
<action type="fix" dev="rbri">
2525
Fixed checkValidity()/reportValidity() and the CSS :valid/:invalid pseudo-classes
26-
for HtmlButtons, HtmlTextarea.
26+
for HtmlButtons, HtmlTextarea, HtmlInput's.
2727
</action>
2828
<action type="fix" dev="rbri">
2929
HtmlFileInput.reset(): a form reset no longer tries to reconstruct a fake File from the

src/main/java/org/htmlunit/javascript/host/html/HTMLInputElement.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,8 @@ public NodeList getLabels() {
732732
*/
733733
@JsxFunction
734734
public boolean checkValidity() {
735-
return getDomNodeOrDie().isValid();
735+
final HtmlInput input = getDomNodeOrDie();
736+
return !input.willValidate() || input.isValid();
736737
}
737738

738739
/**

src/test/java/org/htmlunit/javascript/host/css/CSSSelectorTest.java

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3202,4 +3202,328 @@ public void cssNeitherMatchesTextareaInDisabledFieldset() throws Exception {
32023202

32033203
loadPageVerifyTitle2(html);
32043204
}
3205+
3206+
/**
3207+
* A text input with no 'required' and no custom validity issue
3208+
* matches :valid and not :invalid.
3209+
* @throws Exception if the test fails
3210+
*/
3211+
@Test
3212+
@Alerts({"true", "false"})
3213+
public void cssValidMatchesInputTextWithNoError() throws Exception {
3214+
final String html = DOCTYPE_HTML
3215+
+ "<html><head>\n"
3216+
+ "<script>\n"
3217+
+ LOG_TITLE_FUNCTION
3218+
+ " function test() {\n"
3219+
+ " var t = document.getElementById('t');\n"
3220+
+ " log(t.matches(':valid'));\n"
3221+
+ " log(t.matches(':invalid'));\n"
3222+
+ " }\n"
3223+
+ "</script></head>\n"
3224+
+ "<body onload='test()'>\n"
3225+
+ " <form>\n"
3226+
+ " <input type='text' id='t' value='content'>\n"
3227+
+ " </form>\n"
3228+
+ "</body></html>";
3229+
3230+
loadPageVerifyTitle2(html);
3231+
}
3232+
3233+
/**
3234+
* A text input with a custom validity message matches :invalid and not
3235+
* :valid.
3236+
* @throws Exception if the test fails
3237+
*/
3238+
@Test
3239+
@Alerts({"false", "true"})
3240+
public void cssInvalidMatchesInputTextWithCustomValidity() throws Exception {
3241+
final String html = DOCTYPE_HTML
3242+
+ "<html><head>\n"
3243+
+ "<script>\n"
3244+
+ LOG_TITLE_FUNCTION
3245+
+ " function test() {\n"
3246+
+ " var t = document.getElementById('t');\n"
3247+
+ " t.setCustomValidity('some error');\n"
3248+
+ " log(t.matches(':valid'));\n"
3249+
+ " log(t.matches(':invalid'));\n"
3250+
+ " }\n"
3251+
+ "</script></head>\n"
3252+
+ "<body onload='test()'>\n"
3253+
+ " <form>\n"
3254+
+ " <input type='text' id='t' value='content'>\n"
3255+
+ " </form>\n"
3256+
+ "</body></html>";
3257+
3258+
loadPageVerifyTitle2(html);
3259+
}
3260+
3261+
/**
3262+
* A required, empty text input matches :invalid (valueMissing).
3263+
* @throws Exception if the test fails
3264+
*/
3265+
@Test
3266+
@Alerts({"false", "true"})
3267+
public void cssInvalidMatchesRequiredEmptyInputText() throws Exception {
3268+
final String html = DOCTYPE_HTML
3269+
+ "<html><head>\n"
3270+
+ "<script>\n"
3271+
+ LOG_TITLE_FUNCTION
3272+
+ " function test() {\n"
3273+
+ " var t = document.getElementById('t');\n"
3274+
+ " log(t.matches(':valid'));\n"
3275+
+ " log(t.matches(':invalid'));\n"
3276+
+ " }\n"
3277+
+ "</script></head>\n"
3278+
+ "<body onload='test()'>\n"
3279+
+ " <form>\n"
3280+
+ " <input type='text' id='t' required>\n"
3281+
+ " </form>\n"
3282+
+ "</body></html>";
3283+
3284+
loadPageVerifyTitle2(html);
3285+
}
3286+
3287+
/**
3288+
* A required, non-empty text input matches :valid.
3289+
* @throws Exception if the test fails
3290+
*/
3291+
@Test
3292+
@Alerts({"true", "false"})
3293+
public void cssValidMatchesRequiredNonEmptyInputText() throws Exception {
3294+
final String html = DOCTYPE_HTML
3295+
+ "<html><head>\n"
3296+
+ "<script>\n"
3297+
+ LOG_TITLE_FUNCTION
3298+
+ " function test() {\n"
3299+
+ " var t = document.getElementById('t');\n"
3300+
+ " log(t.matches(':valid'));\n"
3301+
+ " log(t.matches(':invalid'));\n"
3302+
+ " }\n"
3303+
+ "</script></head>\n"
3304+
+ "<body onload='test()'>\n"
3305+
+ " <form>\n"
3306+
+ " <input type='text' id='t' required value='content'>\n"
3307+
+ " </form>\n"
3308+
+ "</body></html>";
3309+
3310+
loadPageVerifyTitle2(html);
3311+
}
3312+
3313+
/**
3314+
* A DISABLED, required,
3315+
* empty text input must match NEITHER :valid NOR :invalid -- disabled bars it
3316+
* from constraint validation entirely, regardless of the would-be
3317+
* valueMissing violation.
3318+
* @throws Exception if the test fails
3319+
*/
3320+
@Test
3321+
@Alerts({"false", "false"})
3322+
public void cssNeitherMatchesDisabledRequiredEmptyInputText() throws Exception {
3323+
final String html = DOCTYPE_HTML
3324+
+ "<html><head>\n"
3325+
+ "<script>\n"
3326+
+ LOG_TITLE_FUNCTION
3327+
+ " function test() {\n"
3328+
+ " var t = document.getElementById('t');\n"
3329+
+ " log(t.matches(':valid'));\n"
3330+
+ " log(t.matches(':invalid'));\n"
3331+
+ " }\n"
3332+
+ "</script></head>\n"
3333+
+ "<body onload='test()'>\n"
3334+
+ " <form>\n"
3335+
+ " <input type='text' id='t' required disabled>\n"
3336+
+ " </form>\n"
3337+
+ "</body></html>";
3338+
3339+
loadPageVerifyTitle2(html);
3340+
}
3341+
3342+
/**
3343+
* A READONLY, required, empty text input. Per spec, readonly bars an element
3344+
* from constraint validation the same way disabled does.
3345+
* @throws Exception if the test fails
3346+
*/
3347+
@Test
3348+
@Alerts({"false", "false"})
3349+
public void cssNeitherMatchesReadonlyRequiredEmptyInputText() throws Exception {
3350+
final String html = DOCTYPE_HTML
3351+
+ "<html><head>\n"
3352+
+ "<script>\n"
3353+
+ LOG_TITLE_FUNCTION
3354+
+ " function test() {\n"
3355+
+ " var t = document.getElementById('t');\n"
3356+
+ " log(t.matches(':valid'));\n"
3357+
+ " log(t.matches(':invalid'));\n"
3358+
+ " }\n"
3359+
+ "</script></head>\n"
3360+
+ "<body onload='test()'>\n"
3361+
+ " <form>\n"
3362+
+ " <input type='text' id='t' required readonly>\n"
3363+
+ " </form>\n"
3364+
+ "</body></html>";
3365+
3366+
loadPageVerifyTitle2(html);
3367+
}
3368+
3369+
/**
3370+
* Same as above but with a custom validity message set as well -- readonly
3371+
* barring must take priority over the custom error too, same as disabled
3372+
* does.
3373+
* @throws Exception if the test fails
3374+
*/
3375+
@Test
3376+
@Alerts({"false", "false"})
3377+
public void cssNeitherMatchesReadonlyInputTextEvenWithCustomValidity() throws Exception {
3378+
final String html = DOCTYPE_HTML
3379+
+ "<html><head>\n"
3380+
+ "<script>\n"
3381+
+ LOG_TITLE_FUNCTION
3382+
+ " function test() {\n"
3383+
+ " var t = document.getElementById('t');\n"
3384+
+ " t.setCustomValidity('some error');\n"
3385+
+ " log(t.matches(':valid'));\n"
3386+
+ " log(t.matches(':invalid'));\n"
3387+
+ " }\n"
3388+
+ "</script></head>\n"
3389+
+ "<body onload='test()'>\n"
3390+
+ " <form>\n"
3391+
+ " <input type='text' id='t' readonly>content\n"
3392+
+ " </form>\n"
3393+
+ "</body></html>";
3394+
3395+
loadPageVerifyTitle2(html);
3396+
}
3397+
3398+
/**
3399+
* Redundant-barring sanity check: disabled AND readonly AND required AND a
3400+
* custom validity message, all at once -- still neither pseudo-class should
3401+
* match.
3402+
* @throws Exception if the test fails
3403+
*/
3404+
@Test
3405+
@Alerts({"false", "false"})
3406+
public void cssNeitherMatchesDisabledAndReadonlyInputText() throws Exception {
3407+
final String html = DOCTYPE_HTML
3408+
+ "<html><head>\n"
3409+
+ "<script>\n"
3410+
+ LOG_TITLE_FUNCTION
3411+
+ " function test() {\n"
3412+
+ " var t = document.getElementById('t');\n"
3413+
+ " t.setCustomValidity('some error');\n"
3414+
+ " log(t.matches(':valid'));\n"
3415+
+ " log(t.matches(':invalid'));\n"
3416+
+ " }\n"
3417+
+ "</script></head>\n"
3418+
+ "<body onload='test()'>\n"
3419+
+ " <form>\n"
3420+
+ " <input type='text' id='t' required disabled readonly>\n"
3421+
+ " </form>\n"
3422+
+ "</body></html>";
3423+
3424+
loadPageVerifyTitle2(html);
3425+
}
3426+
3427+
/**
3428+
* Dynamic transition: a required, empty text starts out correctly
3429+
* :invalid, then is made readonly at runtime -- must stop matching
3430+
* :invalid (or :valid) once barred, confirming the barred state is
3431+
* evaluated fresh rather than cached from page load.
3432+
* @throws Exception if the test fails
3433+
*/
3434+
@Test
3435+
@Alerts({"false", "true", "false", "false", "false", "true"})
3436+
public void cssMatchingUpdatesInputTestWhenReadonlyToggledAtRuntime() throws Exception {
3437+
final String html = DOCTYPE_HTML
3438+
+ "<html><head>\n"
3439+
+ "<script>\n"
3440+
+ LOG_TITLE_FUNCTION
3441+
+ " function test() {\n"
3442+
+ " var t = document.getElementById('t');\n"
3443+
+ " log(t.matches(':valid'));\n"
3444+
+ " log(t.matches(':invalid'));\n"
3445+
3446+
+ " t.readOnly = true;\n"
3447+
+ " log(t.matches(':valid'));\n"
3448+
+ " log(t.matches(':invalid'));\n"
3449+
3450+
+ " t.readOnly = false;\n"
3451+
+ " log(t.matches(':valid'));\n"
3452+
+ " log(t.matches(':invalid'));\n"
3453+
+ " }\n"
3454+
+ "</script></head>\n"
3455+
+ "<body onload='test()'>\n"
3456+
+ " <form>\n"
3457+
+ " <input type='text' id='t' required>\n"
3458+
+ " </form>\n"
3459+
+ "</body></html>";
3460+
3461+
loadPageVerifyTitle2(html);
3462+
}
3463+
3464+
/**
3465+
* Dynamic transition, disabled variant: same idea as above but toggling
3466+
* disabled instead of readonly.
3467+
* @throws Exception if the test fails
3468+
*/
3469+
@Test
3470+
@Alerts({"false", "true", "false", "false", "false", "true"})
3471+
public void cssMatchingInputTextUpdatesWhenDisabledToggledAtRuntime() throws Exception {
3472+
final String html = DOCTYPE_HTML
3473+
+ "<html><head>\n"
3474+
+ "<script>\n"
3475+
+ LOG_TITLE_FUNCTION
3476+
+ " function test() {\n"
3477+
+ " var t = document.getElementById('t');\n"
3478+
+ " log(t.matches(':valid'));\n"
3479+
+ " log(t.matches(':invalid'));\n"
3480+
3481+
+ " t.disabled = true;\n"
3482+
+ " log(t.matches(':valid'));\n"
3483+
+ " log(t.matches(':invalid'));\n"
3484+
3485+
+ " t.disabled = false;\n"
3486+
+ " log(t.matches(':valid'));\n"
3487+
+ " log(t.matches(':invalid'));\n"
3488+
+ " }\n"
3489+
+ "</script></head>\n"
3490+
+ "<body onload='test()'>\n"
3491+
+ " <form>\n"
3492+
+ " <input type='text' id='t' required>\n"
3493+
+ " </form>\n"
3494+
+ "</body></html>";
3495+
3496+
loadPageVerifyTitle2(html);
3497+
}
3498+
3499+
/**
3500+
* Disabled propagated via an enclosing &lt;fieldset disabled&gt; must bar
3501+
* the text input from validation the same way an own 'disabled' attribute
3502+
* does -- exercises the isDisabled() parent-chain walk in combination with
3503+
* CSS matching, not just the element's own attribute.
3504+
* @throws Exception if the test fails
3505+
*/
3506+
@Test
3507+
@Alerts({"false", "false"})
3508+
public void cssNeitherMatchesInputTextInDisabledFieldset() throws Exception {
3509+
final String html = DOCTYPE_HTML
3510+
+ "<html><head>\n"
3511+
+ "<script>\n"
3512+
+ LOG_TITLE_FUNCTION
3513+
+ " function test() {\n"
3514+
+ " var t = document.getElementById('t');\n"
3515+
+ " log(t.matches(':valid'));\n"
3516+
+ " log(t.matches(':invalid'));\n"
3517+
+ " }\n"
3518+
+ "</script></head>\n"
3519+
+ "<body onload='test()'>\n"
3520+
+ " <form>\n"
3521+
+ " <fieldset disabled>\n"
3522+
+ " <input type='text' id='t' required>\n"
3523+
+ " </fieldset>\n"
3524+
+ " </form>\n"
3525+
+ "</body></html>";
3526+
3527+
loadPageVerifyTitle2(html);
3528+
}
32053529
}

0 commit comments

Comments
 (0)