Skip to content
This repository was archived by the owner on Dec 19, 2021. It is now read-only.

Commit b430919

Browse files
OctogonapusAustinShalit
authored andcommitted
Added better port validation (#58)
* Remade PreferencesDialog to look better * Remade PreferencesDialog to look better * Revert "Remade PreferencesDialog to look better" This reverts commit ae384ae * Redid PreferencesDialog to look better and added a default port switch. #46 * Working on port validation #50 * Added test error msg #50 * Using ValidationSupport instead #50 * Disable buttons when port is invalid #50 * Happy with how the text field looks #50 * Added test for disabled buttons #50 * Added test for general invalid ports #50 * Removed old comments #50 * Added more rigorous port validation testing #50 * Made Austin happy #50 * Fixed merge * Increase codecov
1 parent 994b7d3 commit b430919

File tree

5 files changed

+121
-17
lines changed

5 files changed

+121
-17
lines changed

src/main/java/edu/wpi/first/outlineviewer/controller/PreferencesController.java

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
package edu.wpi.first.outlineviewer.controller;
22

3+
import com.google.common.primitives.Ints;
34
import edu.wpi.first.networktables.NetworkTableInstance;
45
import edu.wpi.first.outlineviewer.Preferences;
6+
import java.util.Optional;
57
import javafx.application.Platform;
8+
import javafx.beans.property.BooleanProperty;
9+
import javafx.beans.property.SimpleBooleanProperty;
610
import javafx.fxml.FXML;
711
import javafx.scene.control.TextField;
812
import org.controlsfx.control.ToggleSwitch;
13+
import org.controlsfx.validation.Severity;
14+
import org.controlsfx.validation.ValidationResult;
15+
import org.controlsfx.validation.ValidationSupport;
16+
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
917

1018
/**
1119
* Controller for the app preferences pane.
1220
*/
1321
public class PreferencesController {
14-
1522
@FXML
1623
private ToggleSwitch serverModeSwitch;
1724
@FXML
@@ -21,6 +28,12 @@ public class PreferencesController {
2128
@FXML
2229
private TextField portField;
2330

31+
private final BooleanProperty invalidPortProperty;
32+
33+
public PreferencesController() {
34+
invalidPortProperty = new SimpleBooleanProperty(false);
35+
}
36+
2437
@FXML
2538
private void initialize() {
2639
idField.disableProperty().bind(serverModeSwitch.selectedProperty());
@@ -33,6 +46,36 @@ private void initialize() {
3346

3447
//When the user selects default port from non-default port we need to update the port number
3548
//and port text field to the default port number
49+
50+
defaultPortSwitch.selectedProperty().addListener((observable, newValue, oldValue) -> {
51+
if (defaultPortSwitch.selectedProperty().get()) {
52+
Preferences.setPort(NetworkTableInstance.kDefaultPort);
53+
portField.setText(String.valueOf(Preferences.getPort()));
54+
}
55+
});
56+
57+
//Display error message when the current port number is invalid
58+
ValidationSupport validator = new ValidationSupport();
59+
validator.setValidationDecorator(
60+
new StyleClassValidationDecoration("text-field-error",
61+
"text-field-warning"));
62+
validator.registerValidator(portField, false, (control, value) -> {
63+
if (value instanceof String) {
64+
return ValidationResult.fromMessageIf(control,
65+
"Invalid port number",
66+
Severity.ERROR,
67+
!validatePortNumber((String) value).isPresent());
68+
}
69+
70+
return ValidationResult.fromMessageIf(control,
71+
"Invalid port number",
72+
Severity.ERROR,
73+
false);
74+
});
75+
76+
//Link the outward-facing valid port property to the validation code
77+
invalidPortProperty.bind(validator.invalidProperty());
78+
3679
defaultPortSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> {
3780
if (defaultPortSwitch.selectedProperty().get()) {
3881
portField.setText(String.valueOf(NetworkTableInstance.kDefaultPort));
@@ -51,8 +94,10 @@ private void initialize() {
5194
* Starts running ntcore in the selected mode.
5295
*/
5396
public void save() {
54-
if (portField.getText().matches("[0-9]+")) {
55-
Preferences.setPort(Integer.parseInt(portField.getText()));
97+
Optional<Integer> portNum = validatePortNumber(portField.getText());
98+
99+
if (portNum.isPresent()) {
100+
Preferences.setPort(portNum.get());
56101
} else {
57102
Preferences.setPort(NetworkTableInstance.kDefaultPort);
58103
}
@@ -67,4 +112,22 @@ public void save() {
67112
Preferences.setServer(serverModeSwitch.isSelected());
68113
}
69114

115+
private Optional<Integer> validatePortNumber(String rawPortNumber) {
116+
Optional<Integer> portNum = Optional.empty();
117+
118+
Integer val = Ints.tryParse(rawPortNumber);
119+
if (val != null && val > 0 && val <= 65535) {
120+
portNum = Optional.of(val);
121+
}
122+
123+
return portNum;
124+
}
125+
126+
public boolean isInvalidPort() {
127+
return invalidPortProperty.get();
128+
}
129+
130+
public BooleanProperty invalidPortProperty() {
131+
return invalidPortProperty;
132+
}
70133
}

src/main/java/edu/wpi/first/outlineviewer/view/dialog/PreferencesDialog.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import javafx.scene.control.Dialog;
77

88
import java.io.IOException;
9+
import java.util.Arrays;
910

1011
/**
1112
* A dialog for changing the app preferences.
@@ -29,6 +30,13 @@ public PreferencesDialog(ButtonType... buttonTypes) throws IOException {
2930
getDialogPane().getButtonTypes().addAll(buttonTypes);
3031
setResultConverter(buttonType -> !buttonType.getButtonData().isCancelButton());
3132
controller = loader.getController();
33+
34+
//Bind the disabled property of each button to the valid port property of the controller
35+
//so user can't save with an invalid port
36+
Arrays.stream(buttonTypes)
37+
.filter(btn -> btn.getButtonData().isDefaultButton())
38+
.map(btn -> getDialogPane().lookupButton(btn))
39+
.forEach(btn -> btn.disableProperty().bind(controller.invalidPortProperty()));
3240
}
3341

3442
public PreferencesController getController() {

src/main/resources/edu/wpi/first/outlineviewer/styles.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,14 @@
4949
.list-cell:even:selected, .list-cell:odd:selected {
5050
-fx-background-color: #2196F3;
5151
}
52+
53+
.text-field-error {
54+
-fx-text-box-border: rgba(255, 0, 0, 0.6);
55+
-fx-focus-color: rgba(255, 0, 0, 0.6);
56+
-fx-faint-focus-color: transparent;
57+
}
58+
59+
.text-field-warning {
60+
-fx-text-box-border: yellow;
61+
-fx-focus-color: yellow;
62+
}

src/main/resources/edu/wpi/first/outlineviewer/view/dialog/PreferencesDialog.fxml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
<?import javafx.scene.layout.Pane?>
99
<?import javafx.scene.layout.VBox?>
1010
<?import org.controlsfx.control.ToggleSwitch?>
11+
<?import java.net.URL?>
1112

12-
<AnchorPane xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:id="preferencesPaneRoot" fx:controller="edu.wpi.first.outlineviewer.controller.PreferencesController">
13+
<AnchorPane xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" id="preferencesPaneRoot" fx:id="preferencesPaneRoot" fx:controller="edu.wpi.first.outlineviewer.controller.PreferencesController">
1314
<children>
1415
<VBox layoutX="10.0" layoutY="10.0" spacing="10.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
1516
<children>
@@ -48,4 +49,7 @@
4849
</children>
4950
</VBox>
5051
</children>
52+
<stylesheets>
53+
<URL value="@../../styles.css"/>
54+
</stylesheets>
5155
</AnchorPane>

src/test/java/edu/wpi/first/outlineviewer/controller/PreferencesControllerTest.java

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
package edu.wpi.first.outlineviewer.controller;
22

3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertTrue;
6+
37
import edu.wpi.first.networktables.NetworkTableInstance;
48
import edu.wpi.first.outlineviewer.FxHelper;
59
import edu.wpi.first.outlineviewer.NetworkTableUtilities;
610
import edu.wpi.first.outlineviewer.Preferences;
711
import edu.wpi.first.outlineviewer.view.dialog.PreferencesDialog;
8-
import javafx.fxml.FXMLLoader;
9-
import javafx.scene.Scene;
12+
import javafx.scene.control.ButtonType;
1013
import javafx.scene.control.TextField;
11-
import javafx.scene.layout.Pane;
1214
import javafx.stage.Stage;
1315
import org.controlsfx.control.ToggleSwitch;
14-
import org.junit.jupiter.api.Test;
1516
import org.junit.jupiter.api.AfterEach;
17+
import org.junit.jupiter.api.Test;
18+
import org.junit.jupiter.params.ParameterizedTest;
19+
import org.junit.jupiter.params.provider.CsvSource;
1620
import org.testfx.framework.junit5.ApplicationTest;
1721

18-
import static org.junit.Assert.assertEquals;
19-
import static org.junit.Assert.assertFalse;
20-
import static org.junit.Assert.assertTrue;
21-
2222
public class PreferencesControllerTest extends ApplicationTest {
2323

2424
private PreferencesController controller;
@@ -27,11 +27,9 @@ public class PreferencesControllerTest extends ApplicationTest {
2727
public void start(Stage stage) throws Exception {
2828
Preferences.reset();
2929

30-
FXMLLoader loader
31-
= new FXMLLoader(PreferencesDialog.class.getResource("PreferencesDialog.fxml"));
32-
Pane pane = loader.load();
33-
controller = loader.getController();
34-
stage.setScene(new Scene(pane));
30+
PreferencesDialog dialog = new PreferencesDialog(ButtonType.CANCEL, ButtonType.OK);
31+
controller = dialog.getController();
32+
stage.setScene(dialog.getDialogPane().getScene());
3533
stage.show();
3634
}
3735

@@ -75,6 +73,26 @@ void testIdEmpty() {
7573
assertEquals("localhost", Preferences.getIp());
7674
}
7775

76+
@ParameterizedTest
77+
@CsvSource({"true, 0", "false, 65535", "true, 65536", "true, -1", "true, +1"})
78+
void testPortValidationTextField(boolean result, String port) {
79+
FxHelper.runAndWait(() -> {
80+
((ToggleSwitch) lookup("#defaultPortSwitch").query()).setSelected(false);
81+
((TextField) lookup("#portField").query()).setText(port);
82+
});
83+
assertEquals(result, controller.isInvalidPort());
84+
}
85+
86+
@ParameterizedTest
87+
@CsvSource({"true, 0", "false, 65535", "true, 65536", "true, -1", "true, +1"})
88+
void testPortValidationButton(boolean result, String port) {
89+
FxHelper.runAndWait(() -> {
90+
((ToggleSwitch) lookup("#defaultPortSwitch").query()).setSelected(false);
91+
((TextField) lookup("#portField").query()).setText(port);
92+
});
93+
assertEquals(result, lookup("OK").query().isDisabled());
94+
}
95+
7896
@Test
7997
void testDefaultPortButton() {
8098
FxHelper.runAndWait(() -> {

0 commit comments

Comments
 (0)