Skip to content

Commit 3eed1a4

Browse files
committed
Add scene-key shortcut tests.
1 parent 5f3ef09 commit 3eed1a4

File tree

5 files changed

+342
-1
lines changed

5 files changed

+342
-1
lines changed

src/main/java/com/github/mfl28/boundingboxeditor/controller/Controller.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public class Controller {
180180
private final BooleanProperty navigatePreviousKeyPressed = new SimpleBooleanProperty(false);
181181
private final BooleanProperty navigateNextKeyPressed = new SimpleBooleanProperty(false);
182182
private final IoMetaData ioMetaData = new IoMetaData();
183-
private final List<KeyCombinationEventHandler> keyCombinationHandlers = createKeyCombinationHandlers();
183+
final List<KeyCombinationEventHandler> keyCombinationHandlers = createKeyCombinationHandlers();
184184
String lastLoadedImageUrl;
185185
private final ChangeListener<Number> selectedFileIndexListener = createSelectedFileIndexListener();
186186
Thread directoryWatcher;

src/test/java/com/github/mfl28/boundingboxeditor/BoundingBoxEditorTestBase.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public class BoundingBoxEditorTestBase {
7777
protected static int TIMEOUT_DURATION_IN_SEC = 30;
7878
protected static String TEST_IMAGE_FOLDER_PATH_1 = "/testimages/1";
7979
protected static String TEST_IMAGE_FOLDER_PATH_2 = "/testimages/2";
80+
protected static String TEST_IMAGE_FOLDER_PATH_3 = "/testimages/3";
8081
protected Controller controller;
8182
protected MainView mainView;
8283
protected Model model;
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
/*
2+
* Copyright (C) 2022 Markus Fleischhacker <[email protected]>
3+
*
4+
* This file is part of Bounding Box Editor
5+
*
6+
* Bounding Box Editor is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* Bounding Box Editor is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with Bounding Box Editor. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
package com.github.mfl28.boundingboxeditor.controller;
20+
21+
import com.github.mfl28.boundingboxeditor.BoundingBoxEditorTestBase;
22+
import com.github.mfl28.boundingboxeditor.controller.utils.KeyCombinationEventHandler;
23+
import com.github.mfl28.boundingboxeditor.ui.BoundingPolygonView;
24+
import javafx.application.Platform;
25+
import javafx.geometry.Point2D;
26+
import javafx.scene.input.KeyCode;
27+
import javafx.scene.input.KeyEvent;
28+
import javafx.scene.input.MouseButton;
29+
import javafx.stage.Stage;
30+
import org.hamcrest.Matchers;
31+
import org.junit.jupiter.api.Assertions;
32+
import org.junit.jupiter.api.Tag;
33+
import org.junit.jupiter.api.Test;
34+
import org.junit.jupiter.api.TestInfo;
35+
import org.testfx.api.FxRobot;
36+
import org.testfx.framework.junit5.Start;
37+
import org.testfx.matcher.base.NodeMatchers;
38+
import org.testfx.util.WaitForAsyncUtils;
39+
40+
import java.io.File;
41+
import java.util.concurrent.TimeUnit;
42+
43+
import static org.testfx.api.FxAssert.verifyThat;
44+
45+
@Tag("ui")
46+
class SceneKeyShortcutTests extends BoundingBoxEditorTestBase {
47+
@Start
48+
void start(Stage stage) {
49+
super.onStart(stage);
50+
controller.loadImageFiles(new File(getClass().getResource(TEST_IMAGE_FOLDER_PATH_3).getFile()));
51+
}
52+
53+
@Test
54+
void onSceneKeyPressed_ShouldPerformCorrectAction(TestInfo testinfo, FxRobot robot) {
55+
waitUntilCurrentImageIsLoaded(testinfo);
56+
WaitForAsyncUtils.waitForFxEvents();
57+
58+
verifyThat(controller.keyCombinationHandlers.stream().map(KeyCombinationEventHandler::getKeyCombination).toList(),
59+
Matchers.containsInAnyOrder(
60+
Controller.KeyCombinations.navigateNext, Controller.KeyCombinations.navigatePrevious,
61+
Controller.KeyCombinations.showAllBoundingShapes, Controller.KeyCombinations.hideAllBoundingShapes,
62+
Controller.KeyCombinations.showSelectedBoundingShape, Controller.KeyCombinations.hideSelectedBoundingShape,
63+
Controller.KeyCombinations.resetSizeAndCenterImage, Controller.KeyCombinations.focusCategoryNameTextField,
64+
Controller.KeyCombinations.focusCategorySearchField, Controller.KeyCombinations.focusTagTextField,
65+
Controller.KeyCombinations.focusFileSearchField, Controller.KeyCombinations.deleteSelectedBoundingShape,
66+
Controller.KeyCombinations.selectRectangleDrawingMode, Controller.KeyCombinations.selectPolygonDrawingMode,
67+
Controller.KeyCombinations.selectFreehandDrawingMode, Controller.KeyCombinations.removeEditingVerticesWhenBoundingPolygonSelected,
68+
Controller.KeyCombinations.changeSelectedBoundingShapeCategory,
69+
Controller.KeyCombinations.hideNonSelectedBoundingShapes, Controller.KeyCombinations.simplifyPolygon
70+
));
71+
72+
testNavigateNextKeyEvent(testinfo);
73+
testNavigatePreviousKeyEvent(testinfo);
74+
testSelectFreehandDrawingModeKeyEvent();
75+
testSelectRectangleModeKeyEvent();
76+
testFocusCategorySearchFieldKeyEvent();
77+
testFocusFileSearchKeyEvent();
78+
testFocusCategoryNameTextFieldKeyEvent();
79+
testFocusTagTextFieldKeyEvent();
80+
testSelectPolygonModeKeyEvent();
81+
82+
// Draw a bounding polygon.
83+
enterNewCategory(robot, "dummy", testinfo);
84+
WaitForAsyncUtils.waitForFxEvents();
85+
86+
Double[] targetImageViewPointRatios = {0.25, 0.25, 0.1, 0.6, 0.4, 0.75, 0.75, 0.3};
87+
88+
moveAndClickRelativeToImageView(robot, MouseButton.PRIMARY,
89+
new Point2D(targetImageViewPointRatios[0], targetImageViewPointRatios[1]),
90+
new Point2D(targetImageViewPointRatios[2], targetImageViewPointRatios[3]),
91+
new Point2D(targetImageViewPointRatios[4], targetImageViewPointRatios[5]),
92+
new Point2D(targetImageViewPointRatios[6], targetImageViewPointRatios[7]));
93+
94+
WaitForAsyncUtils.waitForFxEvents();
95+
96+
Assertions.assertDoesNotThrow(() -> WaitForAsyncUtils.waitFor(TIMEOUT_DURATION_IN_SEC, TimeUnit.SECONDS,
97+
() -> mainView.getCurrentBoundingShapes()
98+
.size() == 1),
99+
() -> saveScreenshotAndReturnMessage(testinfo,
100+
"Expected number of bounding polygons " +
101+
"not found in " +
102+
TIMEOUT_DURATION_IN_SEC +
103+
" sec."));
104+
105+
BoundingPolygonView polygon = (BoundingPolygonView) mainView.getCurrentBoundingShapes().get(0);
106+
verifyThat(polygon.isSelected(), Matchers.is(true));
107+
verifyThat(polygon, NodeMatchers.isVisible());
108+
109+
testHideSelectedBoundingShapeKeyEvent(polygon);
110+
testShowSelectedBoundingShapeKeyEvent(polygon);
111+
testHideAllBoundingShapesKeyEvent(polygon);
112+
testShowAllBoundingShapesKeyEvent(polygon);
113+
testRemovePolygonVerticesKeyEvent(robot, targetImageViewPointRatios, polygon);
114+
testSimplifySelectedPolygonKeyEvent(polygon);
115+
testInitiateCategoryChangeKeyEvent(testinfo, robot);
116+
117+
// Draw another polygon.
118+
Double[] targetImageViewPointRatios2 = {0.75, 0.75, 0.75, 0.85, 0.85, 0.85, 0.85, 0.75};
119+
120+
moveAndClickRelativeToImageView(robot, MouseButton.PRIMARY,
121+
new Point2D(targetImageViewPointRatios2[0], targetImageViewPointRatios2[1]),
122+
new Point2D(targetImageViewPointRatios2[2], targetImageViewPointRatios2[3]),
123+
new Point2D(targetImageViewPointRatios2[4], targetImageViewPointRatios2[5]),
124+
new Point2D(targetImageViewPointRatios2[6], targetImageViewPointRatios2[7]));
125+
126+
WaitForAsyncUtils.waitForFxEvents();
127+
128+
Assertions.assertDoesNotThrow(() -> WaitForAsyncUtils.waitFor(TIMEOUT_DURATION_IN_SEC, TimeUnit.SECONDS,
129+
() -> mainView.getCurrentBoundingShapes()
130+
.size() == 2),
131+
() -> saveScreenshotAndReturnMessage(testinfo,
132+
"Expected number of bounding polygons " +
133+
"not found in " +
134+
TIMEOUT_DURATION_IN_SEC +
135+
" sec."));
136+
137+
BoundingPolygonView polygon2 = (BoundingPolygonView) mainView.getCurrentBoundingShapes().get(1);
138+
verifyThat(polygon2.isSelected(), Matchers.is(true));
139+
verifyThat(polygon2, NodeMatchers.isVisible());
140+
141+
testHideNonSelectedShapesKeyEvent(polygon, polygon2);
142+
testRemoveCurrentlySelectedBoundingShapeKeyEvent();
143+
testResetImageViewSizeKeyEvent(robot);
144+
}
145+
146+
private void testResetImageViewSizeKeyEvent(FxRobot robot) {
147+
double originalFitWidth = mainView.getEditorImageView().getFitWidth();
148+
double originalFitHeight = mainView.getEditorImageView().getFitHeight();
149+
150+
robot.moveTo(mainView.getEditorImageView())
151+
.press(KeyCode.CONTROL)
152+
.scroll(-30)
153+
.release(KeyCode.CONTROL);
154+
WaitForAsyncUtils.waitForFxEvents();
155+
156+
verifyThat(mainView.getEditorImageView().getFitWidth(), Matchers.not(Matchers.equalTo(originalFitWidth)));
157+
verifyThat(mainView.getEditorImageView().getFitHeight(), Matchers.not(Matchers.equalTo(originalFitHeight)));
158+
159+
KeyEvent resetImageViewSizeKeyEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.R, false, true,false, false);
160+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(resetImageViewSizeKeyEvent));
161+
WaitForAsyncUtils.waitForFxEvents();
162+
163+
verifyThat(mainView.getEditorImageView().getFitWidth(), Matchers.equalTo(originalFitWidth));
164+
verifyThat(mainView.getEditorImageView().getFitHeight(), Matchers.equalTo(originalFitHeight));
165+
}
166+
167+
private void testRemoveCurrentlySelectedBoundingShapeKeyEvent() {
168+
KeyEvent removeSelectedShapeEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.DELETE, false, false,false, false);
169+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(removeSelectedShapeEvent));
170+
WaitForAsyncUtils.waitForFxEvents();
171+
172+
verifyThat(mainView.getCurrentBoundingShapes(), Matchers.hasSize(1));
173+
}
174+
175+
private void testHideNonSelectedShapesKeyEvent(BoundingPolygonView polygon, BoundingPolygonView polygon2) {
176+
KeyEvent hideNonSelectedShapesEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.H, true, false,false, false);
177+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(hideNonSelectedShapesEvent));
178+
WaitForAsyncUtils.waitForFxEvents();
179+
180+
verifyThat(polygon, NodeMatchers.isInvisible());
181+
verifyThat(polygon2, NodeMatchers.isVisible());
182+
}
183+
184+
private void testInitiateCategoryChangeKeyEvent(TestInfo testinfo, FxRobot robot) {
185+
KeyEvent categoryChangeEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.C, true, false,false, false);
186+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(categoryChangeEvent));
187+
WaitForAsyncUtils.waitForFxEvents();
188+
189+
final Stage changeCategoryStage = timeOutGetTopModalStage(robot, "Change Category", testinfo);
190+
verifyThat(changeCategoryStage, Matchers.notNullValue(), saveScreenshot(testinfo));
191+
timeOutLookUpInStageAndClickOn(robot, changeCategoryStage, "Cancel", testinfo);
192+
WaitForAsyncUtils.waitForFxEvents();
193+
timeOutAssertTopModalStageClosed(robot, "Change Category", testinfo);
194+
}
195+
196+
private void testSimplifySelectedPolygonKeyEvent(BoundingPolygonView polygon) {
197+
int numVertices = polygon.getPoints().size() / 2;
198+
KeyEvent simplifySelectedPolygonEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.S, true, false,false, false);
199+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(simplifySelectedPolygonEvent));
200+
WaitForAsyncUtils.waitForFxEvents();
201+
202+
verifyThat(polygon.getPoints().size() / 2, Matchers.equalTo(numVertices));
203+
}
204+
205+
private void testRemovePolygonVerticesKeyEvent(FxRobot robot, Double[] targetImageViewPointRatios, BoundingPolygonView polygon) {
206+
int numInitialVertices = targetImageViewPointRatios.length / 2;
207+
robot.clickOn("#vertex-handle", MouseButton.MIDDLE);
208+
WaitForAsyncUtils.waitForFxEvents();
209+
210+
KeyEvent removeEditingVerticesEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.DELETE, true, false,false, false);
211+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(removeEditingVerticesEvent));
212+
WaitForAsyncUtils.waitForFxEvents();
213+
214+
verifyThat(polygon.getPoints().size() / 2, Matchers.equalTo(numInitialVertices - 1));
215+
}
216+
217+
private void testShowAllBoundingShapesKeyEvent(BoundingPolygonView polygon) {
218+
KeyEvent showAllBoundingShapeEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.V, false, true,true, false);
219+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(showAllBoundingShapeEvent));
220+
WaitForAsyncUtils.waitForFxEvents();
221+
222+
verifyThat(polygon.isSelected(), Matchers.is(true));
223+
verifyThat(polygon, NodeMatchers.isVisible());
224+
}
225+
226+
private void testHideAllBoundingShapesKeyEvent(BoundingPolygonView polygon) {
227+
KeyEvent hideAllBoundingShapeEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.H, false, true,true, false);
228+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(hideAllBoundingShapeEvent));
229+
WaitForAsyncUtils.waitForFxEvents();
230+
231+
verifyThat(polygon.isSelected(), Matchers.is(true));
232+
verifyThat(polygon, NodeMatchers.isInvisible());
233+
}
234+
235+
private void testShowSelectedBoundingShapeKeyEvent(BoundingPolygonView polygon) {
236+
KeyEvent showSelectedBoundingShapeEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.V, false, true,false, false);
237+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(showSelectedBoundingShapeEvent));
238+
WaitForAsyncUtils.waitForFxEvents();
239+
240+
verifyThat(polygon.isSelected(), Matchers.is(true));
241+
verifyThat(polygon, NodeMatchers.isVisible());
242+
}
243+
244+
private void testHideSelectedBoundingShapeKeyEvent(BoundingPolygonView polygon) {
245+
KeyEvent hideSelectedBoundingShapeEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.H, false, true,false, false);
246+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(hideSelectedBoundingShapeEvent));
247+
WaitForAsyncUtils.waitForFxEvents();
248+
249+
verifyThat(polygon.isSelected(), Matchers.is(true));
250+
verifyThat(polygon, NodeMatchers.isInvisible());
251+
}
252+
253+
private void testSelectPolygonModeKeyEvent() {
254+
KeyEvent selectPolygonModeEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.P, false, true,false, false);
255+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(selectPolygonModeEvent));
256+
WaitForAsyncUtils.waitForFxEvents();
257+
258+
verifyThat(controller.getView().getEditor().getEditorToolBar().getPolygonModeButton().isSelected(), Matchers.is(true));
259+
}
260+
261+
private void testFocusTagTextFieldKeyEvent() {
262+
KeyEvent focusTagTextFieldEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.T, false, true,false, false);
263+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(focusTagTextFieldEvent));
264+
WaitForAsyncUtils.waitForFxEvents();
265+
266+
// No bounding-shapes are selected, therefore tag text-field should be disabled.
267+
verifyThat(controller.getView().getTagInputField().isFocused(), Matchers.is(false));
268+
}
269+
270+
private void testFocusCategoryNameTextFieldKeyEvent() {
271+
KeyEvent focusCategoryNameTextField = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.N, false, true,false, false);
272+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(focusCategoryNameTextField));
273+
WaitForAsyncUtils.waitForFxEvents();
274+
275+
verifyThat(controller.getView().getObjectCategoryInputField().isFocused(), Matchers.is(true));
276+
}
277+
278+
private void testFocusFileSearchKeyEvent() {
279+
KeyEvent focusFileSearchFieldEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.F, false, true,true, false);
280+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(focusFileSearchFieldEvent));
281+
WaitForAsyncUtils.waitForFxEvents();
282+
283+
verifyThat(controller.getView().getImageFileSearchField().isFocused(), Matchers.is(true));
284+
}
285+
286+
private void testFocusCategorySearchFieldKeyEvent() {
287+
KeyEvent focusCategorySearchFieldEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.F, false, true,false, false);
288+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(focusCategorySearchFieldEvent));
289+
WaitForAsyncUtils.waitForFxEvents();
290+
291+
verifyThat(controller.getView().getCategorySearchField().isFocused(), Matchers.is(true));
292+
}
293+
294+
private void testSelectRectangleModeKeyEvent() {
295+
KeyEvent selectRectangleModeEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.K, false, true,false, false);
296+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(selectRectangleModeEvent));
297+
WaitForAsyncUtils.waitForFxEvents();
298+
299+
verifyThat(controller.getView().getEditor().getEditorToolBar().getRectangleModeButton().isSelected(), Matchers.is(true));
300+
}
301+
302+
private void testSelectFreehandDrawingModeKeyEvent() {
303+
KeyEvent selectFreehandDrawingModeEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.S, false, true,false, false);
304+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(selectFreehandDrawingModeEvent));
305+
WaitForAsyncUtils.waitForFxEvents();
306+
307+
verifyThat(controller.getView().getEditor().getEditorToolBar().getFreehandModeButton().isSelected(), Matchers.is(true));
308+
}
309+
310+
private void testNavigatePreviousKeyEvent(TestInfo testinfo) {
311+
KeyEvent navigatePreviousPressedEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.A, false, true, false, false);
312+
313+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(navigatePreviousPressedEvent));
314+
WaitForAsyncUtils.waitForFxEvents();
315+
316+
KeyEvent navigatePreviousReleasedEvent = new KeyEvent(KeyEvent.KEY_RELEASED, "", "", KeyCode.A, false, true, false, false);
317+
Platform.runLater(() -> controller.onRegisterSceneKeyReleased(navigatePreviousReleasedEvent));
318+
WaitForAsyncUtils.waitForFxEvents();
319+
320+
waitUntilCurrentImageIsLoaded(testinfo);
321+
322+
verifyThat(model.getCurrentImageFile().getName(), Matchers.equalTo("rachel-hisko-rEM3cK8F1pk-unsplash.jpg"));
323+
}
324+
325+
private void testNavigateNextKeyEvent(TestInfo testinfo) {
326+
KeyEvent navigateNextPressedEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.D, false, true, false, false);
327+
328+
Platform.runLater(() -> controller.onRegisterSceneKeyPressed(navigateNextPressedEvent));
329+
WaitForAsyncUtils.waitForFxEvents();
330+
331+
KeyEvent navigateNextReleasedEvent = new KeyEvent(KeyEvent.KEY_RELEASED, "", "", KeyCode.D, false, true, false, false);
332+
Platform.runLater(() -> controller.onRegisterSceneKeyReleased(navigateNextReleasedEvent));
333+
WaitForAsyncUtils.waitForFxEvents();
334+
335+
waitUntilCurrentImageIsLoaded(testinfo);
336+
337+
verifyThat(model.getCurrentImageFile().getName(), Matchers.equalTo("wexor-tmg-L-2p8fapOA8-unsplash.jpg"));
338+
}
339+
340+
}
37.1 KB
Loading
37.3 KB
Loading

0 commit comments

Comments
 (0)