From 3f0bd023dc27089831742cd035afe68109a1e347 Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:03:03 +0000 Subject: [PATCH 1/2] feat!: refuse text values the browser physically prevents `TextFieldTester.setValue` and `TextAreaTester.setValue` used to accept a value no user could have typed. `maxLength` truncates the characters over the limit in the browser and `allowedCharPattern` filters out the keystrokes it does not match, so both testers now refuse such a value with an `IllegalArgumentException` instead of committing it and leaving the field invalid. `TextAreaTester.setValue(null)` gets the same guard `TextFieldTester` has, so it fails with that message rather than an opaque reflection wrapper around Flow's `NullPointerException`. The validation-only constraints are untouched: `minLength`, `pattern` and required still commit the value and leave the field invalid, which is the state a validation test needs to reach. Fixes #248 --- CONVENTIONS.md | 8 +- guidelines/testers.md | 13 +- .../textfield/TextAreaTesterTest.java | 37 ++++-- .../textfield/TextFieldTesterTest.java | 27 ++-- .../component/textfield/TextAreaTester.java | 30 ++++- .../component/textfield/TextFieldTester.java | 26 +++- .../textfield/TextInputConstraints.java | 125 ++++++++++++++++++ 7 files changed, 239 insertions(+), 27 deletions(-) create mode 100644 shared/src/main/java/com/vaadin/flow/component/textfield/TextInputConstraints.java diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 2eadf3ff..58769be8 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -52,8 +52,12 @@ with `isValid()` rather than refusing the value at set time. Refuse a value only when the real control physically cannot produce it: a slider clamps to its range and snaps to its step, so `RangeInputTester` and -`NumberSliderTester` do reject out-of-range and off-step values. Structural -refusals stay too, such as `null` on a field whose empty value is not `null`. +`NumberSliderTester` do reject out-of-range and off-step values, and a text +input truncates at `maxLength` and filters the keystrokes `allowedCharPattern` +does not match, so `TextFieldTester` and `TextAreaTester` reject a value that +breaks either one. `minLength`, `pattern` and required are validation-only and +keep committing an invalid value. Structural refusals stay too, such as `null` +on a field whose empty value is not `null`. Read-only state counts towards usability. `isUsable()` is enabled + attached + effectively visible + not inert + not read-only, and effective visibility walks diff --git a/guidelines/testers.md b/guidelines/testers.md index f5865e1c..7e0faf9d 100644 --- a/guidelines/testers.md +++ b/guidelines/testers.md @@ -81,9 +81,20 @@ for it — not enforced at set time. The exception is a control that physically cannot produce the value: a slider clamps to its range and snaps to its step, so `RangeInputTester` and -`NumberSliderTester` do refuse out-of-range and off-step values. Structural +`NumberSliderTester` do refuse out-of-range and off-step values. A text input +truncates what is over `maxLength` and filters out the keystrokes +`allowedCharPattern` does not match, so `TextFieldTester` and `TextAreaTester` +refuse a value that breaks either one — while `minLength`, `pattern` and +required stay validation-only and keep committing an invalid value. Structural refusals stay too, such as `null` on a field whose empty value is not `null`. +Where the line falls is a question about the control, not about the constraint: +ask whether a user sitting in front of the component could hand the field that +value at all. When they could not, refuse it with an `IllegalArgumentException` +whose message says what the browser does instead, rather than silently +correcting the value — a test that asks for the impossible has a bug in it, and +truncating or clamping behind its back would hide it. + `isValid()` means "not marked invalid, and the current value passes the component's own default validator" — it delegates to `getDefaultValidator()` rather than re-checking required / `min` / `max` / `step` by hand. Re-checking diff --git a/junit6/src/test/java/com/vaadin/flow/component/textfield/TextAreaTesterTest.java b/junit6/src/test/java/com/vaadin/flow/component/textfield/TextAreaTesterTest.java index 43f007b2..fee9bd21 100644 --- a/junit6/src/test/java/com/vaadin/flow/component/textfield/TextAreaTesterTest.java +++ b/junit6/src/test/java/com/vaadin/flow/component/textfield/TextAreaTesterTest.java @@ -93,7 +93,7 @@ public void nonInteractableField_throwsOnSetValue() { @Test void textAreaWithValidation_doNotPreventInvalid_doNotThrow() { // Only accept numbers - view.textArea.setAllowedCharPattern("\\d*"); + view.textArea.setPattern("\\d*"); final TextAreaTester