Improve PasswordTextFormatter

Use commitValue() to refresh text field content. Also, add tests.
This commit is contained in:
mkpaz 2023-01-02 13:03:46 +04:00
parent e485b28595
commit 308a4b9b28
2 changed files with 176 additions and 24 deletions

@ -10,7 +10,6 @@ import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;
import java.util.Objects;
import java.util.function.UnaryOperator;
/**
@ -19,7 +18,7 @@ import java.util.function.UnaryOperator;
*/
public class PasswordTextFormatter extends TextFormatter<String> {
public static final char BULLET = '\u2731'; // heavy asterisk
public static final char BULLET = '✱'; // U+2731, heavy asterisk
protected PasswordTextFormatter(StringConverter<String> valueConverter,
UnaryOperator<Change> filter,
@ -39,13 +38,13 @@ public class PasswordTextFormatter extends TextFormatter<String> {
passwordFilter.setInitialText(textField.getText());
revealPasswordProperty().addListener((obs, old, val) -> {
// Force text field update, because converter is only called on focus
// events by default. Don't use commitValue() here because caret position
// won't be correct due to #javafx-bug (https://bugs.openjdk.org/browse/JDK-8248914).
if (val == null) {
return;
}
textField.setText(passwordProperty().get());
if (val == null) { return; }
// Force text field update, because converter is only called on focus events by default.
// Also, reset caret first, because otherwise its position won't be correct due to
// #javafx-bug (https://bugs.openjdk.org/browse/JDK-8248914).
textField.positionCaret(0);
textField.commitValue();
});
// force text field update on scene show
@ -95,15 +94,17 @@ public class PasswordTextFormatter extends TextFormatter<String> {
@Override
public String toString(String s) {
if (s == null) {
return "";
}
return filter.revealPassword.get() ? filter.password.get() : filter.maskText(s.length());
return getPassword();
}
@Override
public String fromString(String string) {
return filter.password.get();
public String fromString(String s) {
return getPassword();
}
protected String getPassword() {
var password = filter.password.get();
return filter.revealPassword.get() ? password : filter.maskText(password.length());
}
}
@ -116,15 +117,6 @@ public class PasswordTextFormatter extends TextFormatter<String> {
@Override
public TextFormatter.Change apply(TextFormatter.Change change) {
// Since we are using setText() to force text field to update (see above),
// we should protect internal password value from changing when `revealPassword`is toggled.
if (Objects.equals(change.getText(), sb.toString())) {
if (!revealPassword.get()) {
change.setText(maskText(change.getText().length()));
}
return change;
}
if (change.isReplaced()) {
sb.replace(change.getRangeStart(), change.getRangeEnd(), change.getText());
} else if (change.isDeleted()) {

@ -0,0 +1,160 @@
package atlantafx.base.util;
import javafx.application.Platform;
import javafx.scene.control.TextField;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class PasswordTextFormatterTest {
@BeforeAll
public static void startup() {
Platform.startup(() -> { });
}
@Test
public void testTextIsMaskedByDefault() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
assertEquals("+".repeat(3), field.getText());
assertEquals("123", fmt.getPassword());
}
@Test
public void testTextCanBeRevealed() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
fmt.setRevealPassword(true);
assertEquals("123", field.getText());
assertEquals("123", fmt.getPassword());
}
@Test
public void testPrependText() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
field.insertText(0, "456");
assertEquals("+".repeat(6), field.getText());
assertEquals("456123", fmt.getPassword());
}
@Test
public void testAppendText() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
field.appendText("456");
assertEquals("+".repeat(6), field.getText());
assertEquals("123456", fmt.getPassword());
}
@Test
public void testInsertText() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
field.insertText(2, "456");
assertEquals("+".repeat(6), field.getText());
assertEquals("124563", fmt.getPassword());
}
@Test
public void testNoInitialText() {
var field = new TextField(null);
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.appendText("456");
assertEquals("+".repeat(3), field.getText());
assertEquals("456", fmt.getPassword());
}
@Test
public void testDeleteSomeText() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
field.deleteText(0, 2);
assertEquals("+", field.getText());
assertEquals("3", fmt.getPassword());
}
@Test
public void testDeleteAllText() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
field.deleteText(0, field.getText().length());
assertEquals("", field.getText());
assertEquals("", fmt.getPassword());
}
@Test
public void testSetTextToNull() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
field.setText(null);
assertNull(null, field.getText());
assertEquals("", fmt.getPassword());
}
@Test
public void testReplaceSelection() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
field.selectRange(1, field.getText().length());
field.replaceSelection("456");
assertEquals("+".repeat(4), field.getText());
assertEquals("1456", fmt.getPassword());
}
@Test
public void testReplaceAll() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123");
field.selectRange(0, field.getText().length());
field.replaceSelection("456");
assertEquals("+".repeat(3), field.getText());
assertEquals("456", fmt.getPassword());
}
@Test
public void testCanContainBullets() {
var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123++");
assertEquals("+".repeat(5), field.getText());
assertEquals("123++", fmt.getPassword());
}
}