Java 应用程序与应用程序测试:不同的事件调度?
目前我正在研究TextField和默认/取消按钮的问题。在使用TestFX测试时,我在事件调度(?)方面遇到了一个差异,这使得测试在应用程序运行时失败 下面是一个非常简化的版本:Java 应用程序与应用程序测试:不同的事件调度?,java,javafx,testfx,Java,Javafx,Testfx,目前我正在研究TextField和默认/取消按钮的问题。在使用TestFX测试时,我在事件调度(?)方面遇到了一个差异,这使得测试在应用程序运行时失败 下面是一个非常简化的版本: 只是一个简单的ui,由框中的文本字段组成,用于应用程序/测试 textField有一个(键-)处理程序,在按下时触发actionEvent textField有一个使用actionEvent的操作处理程序 (key-)处理程序检查操作是否已使用(此处:简单日志,在真实上下文中,如果操作已使用,则必须使用(key-)事
- 只是一个简单的ui,由框中的文本字段组成,用于应用程序/测试
- textField有一个(键-)处理程序,在按下时触发actionEvent
- textField有一个使用actionEvent的操作处理程序
- (key-)处理程序检查操作是否已使用(此处:简单日志,在真实上下文中,如果操作已使用,则必须使用(key-)事件)
- 运行应用程序,按a:注意说明已使用激发的actionEvent的日志
- 运行测试时,请注意说明未使用激发的actionEvent的日志
public class ActionApp extends Application {
// create a simple ui - static because must be same for ActionTest
public static Parent createContent() {
TextField field = new TextField();
// some handler to fire an actionEvent
field.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.A) {
ActionEvent action = new ActionEvent(field, field);
field.fireEvent(action);
LOG.info("action/consumed? " + action + action.isConsumed());
}
});
// another handler to consume the fired action
field.addEventHandler(ActionEvent.ACTION, e -> {
e.consume();
LOG.info("action received " + e + e.isConsumed());
});
VBox actionUI = new VBox(field);
return actionUI;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(ActionApp.class.getName());
}
测试:
public class ActionTest extends ApplicationTest {
/**
* Does not really test anything, just to see the output.
*/
@Test
public void testConsumeA() {
// sanity: focused to receive the key
verifyThat(".text-field", NodeMatchers.isFocused());
press(KeyCode.A);
}
@Override
public void start(Stage stage) {
Parent root = ActionApp.createContent();
Scene scene = new Scene(root, 100, 100);
stage.setScene(scene);
stage.show();
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(ActionTest.class.getName());
}
我的环境是从2018年10月开始在win10上运行fx11和TestFX。仅供参考:打开一个不同之处在于TestFx在存储所有激发事件的阶段上为
EventType.ROOT
注入一个eventFilter。黑客就是要把过滤器取下来,弄得脏兮兮的,就像:
public static void stopStoringFiredEvents() {
FxToolkitContext context = FxToolkit.toolkitContext();
// reflectively access the private field of the context
FiredEvents fired =(FiredEvents) FXUtils.invokeGetFieldValue(FxToolkitContext.class, context, "firedEvents");
// stop recording
fired.stopStoringFiredEvents();
}
/**
* Updated hack, now reaaally dirty: need to manually clear the handler map :(
*/
public static void stopStoringFiredEvents(Stage stage) {
// remove the event-logging filter
stopStoringFiredEvents();
// really cleanup:
// removing the filter only nulls the eventHandler in CompositeEventHandler
// but does not remove the Composite from EventHandlerManager.handlerMap
// as a result, handlerManager.dispatchCapturingEvent runs into the fixForSource
// block which copies the event even though there is no filter
WindowEventDispatcher windowDispatcher = (WindowEventDispatcher) stage.getEventDispatcher();
EventHandlerManager manager = windowDispatcher.getEventHandlerManager();
Map<?, ?> handlerMap = (Map<?, ?>) FXUtils.invokeGetFieldValue(EventHandlerManager.class, manager, "eventHandlerMap");
handlerMap.clear();
}
publicstaticvoid stopStoringFiredEvents(){
FxToolkitContext=FxToolkit.toolkitContext();
//反射式访问上下文的私有字段
FiredEvents fired=(FiredEvents)FXUtils.invokeGetFieldValue(FxToolkitContext.class,context,“FiredEvents”);
//停止录音
fired.stopStoringFiredEvents();
}
/**
*更新的hack,现在真的很脏:需要手动清除处理程序映射:(
*/
公共静态无效停止存储FireDevents(阶段){
//删除事件日志过滤器
停止存储FireDevents();
//真正的清理:
//删除筛选器只会使CompositeEventHandler中的eventHandler为空
//但不会从EventHandlerManager.handlerMap中删除该组合
//因此,handlerManager.dispatchCapturingEvent会运行到fixForSource中
//即使没有筛选器也复制事件的块
WindowEventDispatcher windowDispatcher=(WindowEventDispatcher)stage.getEventDispatcher();
EventHandlerManager=windowDispatcher.getEventHandlerManager();
Map handlerMap=(Map)FXUtils.invokeGetFieldValue(EventHandlerManager.class,manager,“eventHandlerMap”);
handlerMap.clear();
}
虽然这种方法绕过了这个特定的上下文,但在一般情况下并没有可靠的帮助:只要父层次结构中的任何位置存在eventFilter(与激发的事件具有相同或超级eventType),就会发生同样的情况。根本原因似乎是事件分派创建了新的事件实例(通过event.copyFor)因此,在操作处理程序中使用的事件实例与fire发送的实例不同
更新:
- 注意,仅仅删除firedEvents过滤器是没有帮助的(不知道为什么它看起来像几天前的样子……):一旦有了过滤器,事件将被永久地发送到它的包含处理程序,即使它稍后被清空
public static void stopStoringFiredEvents() {
FxToolkitContext context = FxToolkit.toolkitContext();
// reflectively access the private field of the context
FiredEvents fired =(FiredEvents) FXUtils.invokeGetFieldValue(FxToolkitContext.class, context, "firedEvents");
// stop recording
fired.stopStoringFiredEvents();
}
/**
* Updated hack, now reaaally dirty: need to manually clear the handler map :(
*/
public static void stopStoringFiredEvents(Stage stage) {
// remove the event-logging filter
stopStoringFiredEvents();
// really cleanup:
// removing the filter only nulls the eventHandler in CompositeEventHandler
// but does not remove the Composite from EventHandlerManager.handlerMap
// as a result, handlerManager.dispatchCapturingEvent runs into the fixForSource
// block which copies the event even though there is no filter
WindowEventDispatcher windowDispatcher = (WindowEventDispatcher) stage.getEventDispatcher();
EventHandlerManager manager = windowDispatcher.getEventHandlerManager();
Map<?, ?> handlerMap = (Map<?, ?>) FXUtils.invokeGetFieldValue(EventHandlerManager.class, manager, "eventHandlerMap");
handlerMap.clear();
}