Improve PasswordTextFormatter
Use commitValue() to refresh text field content. Also, add tests.
This commit is contained in:
parent
e485b28595
commit
308a4b9b28
@ -10,7 +10,6 @@ import javafx.scene.control.TextField;
|
|||||||
import javafx.scene.control.TextFormatter;
|
import javafx.scene.control.TextFormatter;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,7 +18,7 @@ import java.util.function.UnaryOperator;
|
|||||||
*/
|
*/
|
||||||
public class PasswordTextFormatter extends TextFormatter<String> {
|
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,
|
protected PasswordTextFormatter(StringConverter<String> valueConverter,
|
||||||
UnaryOperator<Change> filter,
|
UnaryOperator<Change> filter,
|
||||||
@ -39,13 +38,13 @@ public class PasswordTextFormatter extends TextFormatter<String> {
|
|||||||
passwordFilter.setInitialText(textField.getText());
|
passwordFilter.setInitialText(textField.getText());
|
||||||
|
|
||||||
revealPasswordProperty().addListener((obs, old, val) -> {
|
revealPasswordProperty().addListener((obs, old, val) -> {
|
||||||
// Force text field update, because converter is only called on focus
|
if (val == null) { return; }
|
||||||
// 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).
|
// Force text field update, because converter is only called on focus events by default.
|
||||||
if (val == null) {
|
// Also, reset caret first, because otherwise its position won't be correct due to
|
||||||
return;
|
// #javafx-bug (https://bugs.openjdk.org/browse/JDK-8248914).
|
||||||
}
|
textField.positionCaret(0);
|
||||||
textField.setText(passwordProperty().get());
|
textField.commitValue();
|
||||||
});
|
});
|
||||||
|
|
||||||
// force text field update on scene show
|
// force text field update on scene show
|
||||||
@ -95,15 +94,17 @@ public class PasswordTextFormatter extends TextFormatter<String> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(String s) {
|
public String toString(String s) {
|
||||||
if (s == null) {
|
return getPassword();
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return filter.revealPassword.get() ? filter.password.get() : filter.maskText(s.length());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String fromString(String string) {
|
public String fromString(String s) {
|
||||||
return filter.password.get();
|
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
|
@Override
|
||||||
public TextFormatter.Change apply(TextFormatter.Change change) {
|
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()) {
|
if (change.isReplaced()) {
|
||||||
sb.replace(change.getRangeStart(), change.getRangeEnd(), change.getText());
|
sb.replace(change.getRangeStart(), change.getRangeEnd(), change.getText());
|
||||||
} else if (change.isDeleted()) {
|
} 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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user