Skip to content

Commit 94bcf3f

Browse files
Jeanette Winzenburgaghaisas
Jeanette Winzenburg
authored andcommitted
8231692: Test Infrastructure: enhance KeyEventFirer to inject keyEvents into scene
Reviewed-by: aghaisas
1 parent 286d1b5 commit 94bcf3f

File tree

2 files changed

+228
-4
lines changed

2 files changed

+228
-4
lines changed

modules/javafx.controls/src/test/java/test/com/sun/javafx/scene/control/infrastructure/KeyEventFirer.java

+57-4
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,60 @@
2727

2828
import java.util.Arrays;
2929
import java.util.List;
30+
import java.util.Objects;
31+
32+
import javafx.event.Event;
33+
import javafx.event.EventTarget;
3034
import javafx.event.EventType;
35+
import javafx.scene.Scene;
3136
import javafx.scene.input.KeyCode;
3237
import javafx.scene.input.KeyEvent;
33-
import javafx.event.Event;
34-
import javafx.event.EventTarget;
3538

3639

3740
public class KeyEventFirer {
3841

3942
private final EventTarget target;
40-
43+
private final Scene scene;
44+
45+
/**
46+
* Instantiates a KeyEventFirer on the given event target. KeyEvents are
47+
* fired directly onto the target.
48+
*
49+
* <p>
50+
* Beware: using this constructor on an <code>EventTarget</code> of type <code>Node</code>
51+
* which is not focusOwner may lead
52+
* to false greens (see https://bugs.openjdk.java.net/browse/JDK-8231692).
53+
*
54+
* @param target the target to fire keyEvents onto, must not be null.
55+
* @throws NullPointerException if target is null.
56+
*/
4157
public KeyEventFirer(EventTarget target) {
58+
this(Objects.requireNonNull(target), null);
59+
}
60+
61+
/**
62+
* Instantiates a KeyEventFirer for the given target and scene.
63+
* Any one of those can be null, but not both. A null/not null scene decides
64+
* about the delivering path of events. If null, events are delivered
65+
* via <code>EventUtils.fire(target, keyEvent)</code>, otherwise via
66+
* <code>scene.processKeyEvent(keyEvent)</code>.
67+
* <p>
68+
* Note that in the latter case, the target doesn't matter - the scene
69+
* delivers keyEvents to its focusOwner. Calling code is responsible to
70+
* setup focus state as required.
71+
*
72+
* @param target eventTarget used to create the event for and fire events onto
73+
* if there's no scene
74+
* @param scene to use for delivering events to its focusOwner if not null
75+
*
76+
* @throws NullPointerException if both target and scene are null
77+
*/
78+
public KeyEventFirer(EventTarget target, Scene scene) {
4279
this.target = target;
80+
this.scene = scene;
81+
if (target == null && scene == null) {
82+
throw new NullPointerException("both target and scene are null");
83+
}
4384
}
4485

4586
public void doUpArrowPress(KeyModifier... modifiers) {
@@ -66,9 +107,21 @@ public void doKeyTyped(KeyCode keyCode, KeyModifier... modifiers) {
66107
fireEvents(createEvent(keyCode, KeyEvent.KEY_TYPED, modifiers));
67108
}
68109

110+
/**
111+
* Dispatches the given events. The process depends on the state of
112+
* this firer. If the scene is null, the events are delivered via
113+
* Event.fireEvent(target,..), otherwise they are delivered via
114+
* scene.processKeyEvent.
115+
*
116+
* @param events the events to dispatch.
117+
*/
69118
private void fireEvents(KeyEvent... events) {
70119
for (KeyEvent evt : events) {
71-
Event.fireEvent(target, evt);
120+
if (scene != null) {
121+
scene.processKeyEvent(evt);
122+
} else {
123+
Event.fireEvent(target, evt);
124+
}
72125
}
73126
}
74127

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package test.com.sun.javafx.scene.control.infrastructure;
27+
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
31+
import org.junit.After;
32+
import org.junit.Before;
33+
import org.junit.Test;
34+
35+
import static javafx.scene.input.KeyCode.*;
36+
import static javafx.scene.input.KeyEvent.*;
37+
import static org.junit.Assert.*;
38+
39+
import javafx.scene.Node;
40+
import javafx.scene.Scene;
41+
import javafx.scene.control.Button;
42+
import javafx.scene.control.TextField;
43+
import javafx.scene.input.KeyEvent;
44+
import javafx.scene.layout.Pane;
45+
import javafx.scene.layout.VBox;
46+
import javafx.stage.Stage;
47+
48+
/**
49+
* Test of enhanced KeyEventFirer.
50+
*/
51+
public class KeyEventFirerTest {
52+
53+
private TextField textField;
54+
private Button button;
55+
private Pane root;
56+
private Stage stage;
57+
private Scene scene;
58+
59+
/**
60+
* Test that keyEvent is delivered to focused control and nowhere else.
61+
*/
62+
@Test
63+
public void testFireViaScene() {
64+
showAndFocus(button);
65+
List<KeyEvent> buttonEvents = new ArrayList<>();
66+
button.addEventHandler(KEY_PRESSED, buttonEvents::add);
67+
List<KeyEvent> textFieldEvents = new ArrayList<>();
68+
textField.addEventHandler(KEY_PRESSED, textFieldEvents::add);
69+
KeyEventFirer firer = new KeyEventFirer(textField, scene);
70+
firer.doKeyPress(A);
71+
assertEquals("button must have received the key", 1, buttonEvents.size());
72+
assertEquals("textField must not have received the key", 0, textFieldEvents.size());
73+
}
74+
75+
/**
76+
* Test that keyEvent is delivered to focused control and nowhere else.
77+
* Here we test that the target is not required.
78+
*/
79+
@Test
80+
public void testFireViaSceneNullTarget() {
81+
showAndFocus(button);
82+
List<KeyEvent> buttonEvents = new ArrayList<>();
83+
button.addEventHandler(KEY_PRESSED, buttonEvents::add);
84+
List<KeyEvent> textFieldEvents = new ArrayList<>();
85+
textField.addEventHandler(KEY_PRESSED, textFieldEvents::add);
86+
KeyEventFirer firer = new KeyEventFirer(null, scene);
87+
firer.doKeyPress(A);
88+
assertEquals("button must have received the key", 1, buttonEvents.size());
89+
assertEquals("textField must not have received the key", 0, textFieldEvents.size());
90+
}
91+
92+
/**
93+
* This simulates a false positive: even though not focused, the textField handlers
94+
* are notified when firing directly. That's possible, but typically not what we want to test!
95+
*/
96+
@Test
97+
public void testFireTargetFalseGreen() {
98+
showAndFocus(button);
99+
List<KeyEvent> buttonEvents = new ArrayList<>();
100+
button.addEventHandler(KEY_PRESSED, buttonEvents::add);
101+
List<KeyEvent> textFieldEvents = new ArrayList<>();
102+
textField.addEventHandler(KEY_PRESSED, textFieldEvents::add);
103+
// firing on a node that is not focusOwner
104+
KeyEventFirer incorrectFirer = new KeyEventFirer(textField);
105+
incorrectFirer.doKeyPress(A);
106+
int falseTextFieldNotification = textFieldEvents.size();
107+
int falseButtonNotification = buttonEvents.size();
108+
assertEquals("false green - textField must have received the key", 1, textFieldEvents.size());
109+
assertEquals("false green - button must not have received the key", 0, buttonEvents.size());
110+
textFieldEvents.clear();
111+
buttonEvents.clear();
112+
// firing on the scene makes a difference
113+
KeyEventFirer correctFirer = new KeyEventFirer(null, scene);
114+
correctFirer.doKeyPress(A);
115+
assertEquals(falseTextFieldNotification - 1, textFieldEvents.size());
116+
assertEquals(falseButtonNotification + 1, buttonEvents.size());
117+
}
118+
119+
@Test (expected= NullPointerException.class)
120+
public void testTwoParamConstructorNPE() {
121+
new KeyEventFirer(null, null);
122+
}
123+
124+
@Test (expected= NullPointerException.class)
125+
public void testSingleParamConstructorNPE() {
126+
new KeyEventFirer(null);
127+
}
128+
129+
/**
130+
* Need all: stage.show, stage.requestFocus and control.requestFocus to
131+
* have consistent focused state on control (that is focusOwner and isFocused)
132+
*/
133+
@Test
134+
public void testUIState() {
135+
assertEquals(List.of(button, textField), root.getChildren());
136+
stage.show();
137+
stage.requestFocus();
138+
button.requestFocus();
139+
assertEquals(button, scene.getFocusOwner());
140+
assertTrue(button.isFocused());
141+
}
142+
143+
private void showAndFocus(Node focused) {
144+
stage.show();
145+
stage.requestFocus();
146+
if (focused != null) {
147+
focused.requestFocus();
148+
assertTrue(focused.isFocused());
149+
assertSame(focused, scene.getFocusOwner());
150+
}
151+
}
152+
153+
@Before
154+
public void setup() {
155+
root = new VBox();
156+
scene = new Scene(root);
157+
stage = new Stage();
158+
stage.setScene(scene);
159+
button = new Button("I'm a button");
160+
textField = new TextField("some text");
161+
root.getChildren().addAll(button, textField);
162+
}
163+
164+
@After
165+
public void cleanup() {
166+
if (stage != null) {
167+
stage.hide();
168+
}
169+
}
170+
171+
}

0 commit comments

Comments
 (0)