Skip to content

Commit 198596b

Browse files
committed
added tests
1 parent d110710 commit 198596b

File tree

2 files changed

+302
-7
lines changed

2 files changed

+302
-7
lines changed

modules/javafx.graphics/src/shims/java/javafx/scene/SceneShim.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,8 +25,14 @@
2525

2626
package javafx.scene;
2727

28+
import javafx.scene.input.KeyEvent;
29+
2830
public class SceneShim {
2931

32+
public static void processKeyEvent(Scene s, KeyEvent e) {
33+
s.processKeyEvent(e);
34+
}
35+
3036
public static void focusCleanup(Scene s) {
3137
s.focusCleanup();
3238
}

modules/javafx.graphics/src/test/java/test/javafx/scene/FocusTest.java

+295-6
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,10 @@
5454

5555
import org.junit.jupiter.api.AfterEach;
5656
import org.junit.jupiter.api.BeforeEach;
57+
import org.junit.jupiter.api.Nested;
5758
import org.junit.jupiter.api.Test;
58-
import static org.junit.jupiter.api.Assertions.assertEquals;
59-
import static org.junit.jupiter.api.Assertions.assertFalse;
60-
import static org.junit.jupiter.api.Assertions.assertNull;
61-
import static org.junit.jupiter.api.Assertions.assertSame;
62-
import static org.junit.jupiter.api.Assertions.assertTrue;
63-
import static org.junit.jupiter.api.Assertions.fail;
59+
60+
import static org.junit.jupiter.api.Assertions.*;
6461

6562
public class FocusTest {
6663

@@ -121,6 +118,12 @@ private void assertIsFocused(Node n) {
121118
assertTrue(n.getPseudoClassStates().stream().anyMatch(pc -> pc.getPseudoClassName().equals("focused")));
122119
}
123120

121+
private void assertIsFocused(Node... ns) {
122+
for (Node n : ns) {
123+
assertIsFocused(n);
124+
}
125+
}
126+
124127
private void assertIsFocused(Scene s, Node n) {
125128
assertEquals(n, s.getFocusOwner());
126129
assertIsFocused(n);
@@ -131,6 +134,12 @@ private void assertNotFocused(Node n) {
131134
assertFalse(n.getPseudoClassStates().stream().anyMatch(pc -> pc.getPseudoClassName().equals("focused")));
132135
}
133136

137+
private void assertNotFocused(Node... ns) {
138+
for (Node n : ns) {
139+
assertNotFocused(n);
140+
}
141+
}
142+
134143
private void assertNotFocused(Scene s, Node n) {
135144
assertTrue(n != s.getFocusOwner());
136145
assertNotFocused(n);
@@ -1161,4 +1170,284 @@ class N extends Group {
11611170
assertNotFocusWithin(node2);
11621171
}
11631172

1173+
@Nested
1174+
class FocusDelegateTest {
1175+
static class N extends Group {
1176+
final Node delegate;
1177+
final String name;
1178+
1179+
N(String name, Node child, Node delegate) {
1180+
super(child == null ? new Node[0] : new Node[] { child });
1181+
this.delegate = delegate;
1182+
this.name = name;
1183+
}
1184+
1185+
N(Node child, Node delegate) {
1186+
this(null, child, delegate);
1187+
}
1188+
1189+
@Override
1190+
protected Node getFocusDelegate() {
1191+
return delegate;
1192+
}
1193+
1194+
@Override
1195+
public String toString() {
1196+
return name;
1197+
}
1198+
}
1199+
1200+
static class Trace extends ArrayList<String> {
1201+
Trace(Node... nodes) {
1202+
for (Node node : nodes) {
1203+
node.addEventFilter(
1204+
Event.ANY,
1205+
e -> add(String.format("EventFilter[source=%s, target=%s]", e.getSource(), e.getTarget())));
1206+
1207+
node.addEventHandler(
1208+
Event.ANY,
1209+
e -> add(String.format("EventHandler[source=%s, target=%s]", e.getSource(), e.getTarget())));
1210+
}
1211+
}
1212+
}
1213+
1214+
/**
1215+
* Focusing a node that delegates focus to a descendant will focus both the original node
1216+
* and its descendant. This test asserts that this also works in nested scenarios, when the
1217+
* delegation target delegates focus further down the scene graph.
1218+
*/
1219+
@Test
1220+
void focusIsDelegatedToDescendants() {
1221+
N node1, node2, node3, node4, node5;
1222+
1223+
// node1 . . . . . . . . . focusDelegate = node3
1224+
// └── node2
1225+
// └── node3 . . . . . focusDelegate = node5
1226+
// └── node4
1227+
// └── node5
1228+
scene.setRoot(
1229+
node1 = new N(
1230+
node2 = new N(
1231+
node3 = new N(
1232+
node4 = new N(
1233+
node5 = new N(null, null), null
1234+
), node5
1235+
), null
1236+
), node3
1237+
));
1238+
1239+
assertNotFocused(node1, node2, node3, node4, node5);
1240+
1241+
node1.requestFocus();
1242+
1243+
assertIsFocused(node1, node3, node5);
1244+
assertIsFocused(scene, node1);
1245+
}
1246+
1247+
/**
1248+
* This test is similar to {@link #focusIsDelegatedToDescendants()}, except that instead of
1249+
* focusing the root node, the intermediate focus delegate is focused. This will result in
1250+
* the root node being unfocused, while the intermediate focus delegate and its delegation
1251+
* target are focused.
1252+
*/
1253+
@Test
1254+
void focusIsDelegatedToSubtreeDescendants() {
1255+
N node1, node2, node3, node4, node5;
1256+
1257+
// node1 . . . . . . . . . focusDelegate = node3
1258+
// └── node2
1259+
// └── node3 . . . . . focusDelegate = node5
1260+
// └── node4
1261+
// └── node5
1262+
scene.setRoot(
1263+
node1 = new N(
1264+
node2 = new N(
1265+
node3 = new N(
1266+
node4 = new N(
1267+
node5 = new N(null, null), null
1268+
), node5
1269+
), null
1270+
), node3
1271+
));
1272+
1273+
assertNotFocused(node1, node2, node3, node4, node5);
1274+
1275+
node3.requestFocus();
1276+
1277+
assertIsFocused(node3, node5);
1278+
assertIsFocused(scene, node3);
1279+
}
1280+
1281+
/**
1282+
* When a scene sends a key event to the focus owner, it is delivered up to the final target
1283+
* of delegation. The event target changes as the event travels through the scene graph:
1284+
* First, the event is targeted at the focus owner. For each descendant node, the event is
1285+
* targeted at the next focus delegate. When the capturing phase completes and the event starts
1286+
* to bubble up, it is targeted back to its original targets as it crosses the boundary between
1287+
* focus delegates.
1288+
*/
1289+
@Test
1290+
void keyEventIsDeliveredToFocusedDescendants() {
1291+
N node1, node2, node3, node4, node5;
1292+
1293+
// node1 . . . . . . . . . focusDelegate = node3, focusOwner
1294+
// └── node2
1295+
// └── node3 . . . . . focusDelegate = node5
1296+
// └── node4
1297+
// └── node5
1298+
scene.setRoot(
1299+
node1 = new N("node1",
1300+
node2 = new N("node2",
1301+
node3 = new N("node3",
1302+
node4 = new N("node4",
1303+
node5 = new N("node5",null, null), null
1304+
), node5
1305+
), null
1306+
), node3
1307+
));
1308+
1309+
node1.requestFocus();
1310+
1311+
var trace = new Trace(node1, node2, node3, node4, node5);
1312+
1313+
SceneShim.processKeyEvent(
1314+
scene, new KeyEvent(KeyEvent.KEY_PRESSED, null, null, KeyCode.A, false, false, false, false));
1315+
1316+
assertEquals(
1317+
List.of(
1318+
"EventFilter[source=node1, target=node1]", // <-- target is node1 (focusOwner)
1319+
"EventFilter[source=node2, target=node3]", // <-- change target to node3 (delegate)
1320+
"EventFilter[source=node3, target=node3]",
1321+
"EventFilter[source=node4, target=node5]", // <-- change target to node5 (delegate)
1322+
"EventFilter[source=node5, target=node5]",
1323+
"EventHandler[source=node5, target=node5]",
1324+
"EventHandler[source=node4, target=node5]",
1325+
"EventHandler[source=node3, target=node3]", // <-- change target back to node3
1326+
"EventHandler[source=node2, target=node3]",
1327+
"EventHandler[source=node1, target=node1]" // <-- change target back to node1
1328+
),
1329+
trace
1330+
);
1331+
}
1332+
}
1333+
1334+
@Nested
1335+
class FocusScopeTest {
1336+
static class N extends Group {
1337+
final boolean focusScope;
1338+
1339+
N(boolean focusScope, boolean hoistFocus, Node child) {
1340+
super(child == null ? new Node[0] : new Node[] { child });
1341+
this.focusScope = focusScope;
1342+
setHoistFocus(hoistFocus);
1343+
}
1344+
1345+
@Override
1346+
protected boolean isFocusScope() {
1347+
return focusScope;
1348+
}
1349+
}
1350+
1351+
/**
1352+
* A node with the {@link Node#hoistFocusProperty()} flag will hoist a focus request to the next
1353+
* focus scope. The result is that the node that defines the focus scope is focused, and the node
1354+
* that initially received the focus request is not.
1355+
*/
1356+
@Test
1357+
void hoistFocusRequestToNextScope() {
1358+
N node1, node2, node3, node4, node5;
1359+
1360+
// node1 . . . . . . . . . . [hoistFocus]
1361+
// └── node2 . . . . . . . . [hoistFocus]
1362+
// └── node3 . . . . . . [focusScope, hoistFocus]
1363+
// └── node4 . . . . [hoistFocus]
1364+
// └── node5 . . [hoistFocus]
1365+
scene.setRoot(
1366+
node1 = new N(false, false,
1367+
node2 = new N(false, true,
1368+
node3 = new N(true, true,
1369+
node4 = new N(false, true,
1370+
node5 = new N(false, true, null)
1371+
)
1372+
)
1373+
)
1374+
));
1375+
1376+
node4.requestFocus();
1377+
assertIsFocused(node3);
1378+
assertNotFocused(node1, node2, node4, node5);
1379+
1380+
node5.requestFocus();
1381+
assertIsFocused(node3);
1382+
assertNotFocused(node1, node2, node4, node5);
1383+
}
1384+
1385+
/**
1386+
* A node with the {@link Node#hoistFocusProperty()} flag will hoist a focus request to the next
1387+
* focus scope. In this test, the result is that the node that defines the outermost focus scope
1388+
* is focused, and all other nodes are not focused (this includes the intermediate focus scope).
1389+
*/
1390+
@Test
1391+
void hoistFocusRequestToOutermostScope() {
1392+
N node1, node2, node3, node4, node5;
1393+
1394+
// node1 . . . . . . . . . . [focusScope]
1395+
// └── node2 . . . . . . . . [hoistFocus]
1396+
// └── node3 . . . . . . [focusScope, hoistFocus]
1397+
// └── node4 . . . . [hoistFocus]
1398+
// └── node5 . . [hoistFocus]
1399+
scene.setRoot(
1400+
node1 = new N(true, false,
1401+
node2 = new N(false, true,
1402+
node3 = new N(true, true,
1403+
node4 = new N(false, true,
1404+
node5 = new N(false, true, null)
1405+
)
1406+
)
1407+
)
1408+
));
1409+
1410+
node4.requestFocus();
1411+
assertIsFocused(node1);
1412+
assertNotFocused(node2, node3, node4, node5);
1413+
1414+
node5.requestFocus();
1415+
assertIsFocused(node1);
1416+
assertNotFocused(node2, node3, node4, node5);
1417+
}
1418+
1419+
/**
1420+
* A node with the {@link Node#hoistFocusProperty()} flag will hoist a focus request to the next
1421+
* focus scope. If the next focus scope does not have the {@link Node#hoistFocusProperty()} flag,
1422+
* the focus request will not be hoisted again, even when an ancestor defines another focus scope.
1423+
*/
1424+
@Test
1425+
void hoistFocusRequestStopsWhenHoistFocusIsFalse() {
1426+
N node1, node2, node3, node4, node5;
1427+
1428+
// node1 . . . . . . . . . . [focusScope]
1429+
// └── node2 . . . . . . . . [hoistFocus]
1430+
// └── node3 . . . . . . [focusScope]
1431+
// └── node4 . . . . [hoistFocus]
1432+
// └── node5 . . [hoistFocus]
1433+
scene.setRoot(
1434+
node1 = new N(true, false,
1435+
node2 = new N(false, true,
1436+
node3 = new N(true, false,
1437+
node4 = new N(false, true,
1438+
node5 = new N(false, true, null)
1439+
)
1440+
)
1441+
)
1442+
));
1443+
1444+
node4.requestFocus();
1445+
assertIsFocused(node3);
1446+
assertNotFocused(node1, node2, node4, node5);
1447+
1448+
node5.requestFocus();
1449+
assertIsFocused(node3);
1450+
assertNotFocused(node1, node2, node4, node5);
1451+
}
1452+
}
11641453
}

0 commit comments

Comments
 (0)