我们正在将大型 Swing 应用程序迁移到 Compose Desktop,并打算通过现有 Swing UI 中的组合组件来替换 Swing 组件,例如将带有 TextFields 的可组合组件放入像
JPanel
这样的 Swing 容器中。我们使用 ComposePanel 来执行此操作,效果很好。可以使用 ComposeTestRule 对各个 compose 组件进行单元测试。然而,我们需要对包含 Swing 组件和新组件的应用程序进行更高级别的测试,这意味着将键盘和鼠标输入发送到“嵌入式”组合组件并读取它们的状态。有没有办法做到这一点?理想情况下,我们希望由 ComposeTestRule
提供匹配器和断言,但即使可组合项包含在 ComposePanel
或 ComposeWindow
中,它们也可用。
这是我们想要实现的目标的一个简单示例。 可以使用
MyTextField
轻松地对 ComposeTestRule
的状态进行单元测试:
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun `text field should be initialised to bar and also editable`() {
with (composeTestRule) {
setContent {
MyTextField()
}
onNodeWithText("bar").assertExists()
onNodeWithTag("myTextField").performTextInput("foo ")
onNodeWithText("foo bar").assertExists()
}
}
@Composable
@Preview
fun MyTextField() {
var text by remember { mutableStateOf("bar") }
MaterialTheme {
TextField(
value =text,
onValueChange = { changedText ->
text = changedText
println("changedText = ${changedText}")
},
modifier = Modifier.testTag("myTextField")
)
}
}
但是当使用 ComposePanel 将其嵌入到 JFrame 中时,如何以编程方式执行相同类型的交互和检查?
fun main() {
SwingUtilities.invokeLater {
val composePanel = ComposePanel().apply {
setContent {
MyTextField()
}
}
with(JFrame("compose test")) {
add(composePanel)
setSize(400, 200)
isVisible = true
}
}
} (edited)
这是一个 Compose for Desktop 应用程序。
https://github.com/philipplackner/StopWatchComposeDesktop
这是它的样子。
我假设您知道如何设置它。我将用它来展示一个超级粗略的示例,说明如何自动化测试,即使它们没有与 UI 挂钩。显然,请将其完善为适当的东西。
我启动秒表应用程序,并将其放在后台。然后,我制作以下 Java 文件。
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
public class SettingTheComposeDesktopUpWasExcruciatingKotlinMakesMeVomitSwingIsSuperior
{
public static void main(final String[] args) throws AWTException
{
final Robot robot = new Robot();
System.out.println("Waiting");
robot.delay(5_000); //waits 5 seconds
System.out.println("Pressing ENTER");
robot.keyPress(KeyEvent.VK_ENTER); //press the enter button
robot.keyRelease(KeyEvent.VK_ENTER); //release the enter button
System.out.println("Waiting");
robot.delay(5_000); //waits 5 seconds
System.out.println("Pressing TAB");
robot.keyPress(KeyEvent.VK_TAB); //presses the tab button
robot.keyRelease(KeyEvent.VK_TAB); //release the tab button
System.out.println("Pressing ENTER");
robot.keyPress(KeyEvent.VK_ENTER); //presses the enter button
robot.keyRelease(KeyEvent.VK_ENTER); //release the enter button
}
}
好的,现在我们已经在后台运行了秒表,并且保存了 java 文件,只需编译它,运行它,然后很快地切换到秒表应用程序(你有 5 秒,所以快点)。从那里,机器人将开始使用秒表。
您所做的只是自动化按键。您只需使用键盘快捷键来控制应用程序。
您所要做的就是通过选项卡选择您想要的功能。
现在,如果 TAB 对您来说不够用(而且几乎肯定会不够),这里是添加组合键的方法。
在秒表中,将 Main.kt 文件编辑为以下内容。
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
import androidx.compose.material.MaterialTheme
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
@Composable
@Preview
fun App(
stopWatch: StopWatch,
onStartClick: () -> Unit,
onPauseClick: () -> Unit,
onResetClick: () -> Unit) {
MaterialTheme {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
StopWatchDisplay(
formattedTime = stopWatch.formattedTime,
onStartClick,
onPauseClick,
onResetClick
)
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
fun main() = application {
val stopWatch = remember { StopWatch() };
val onStartClick = stopWatch::start
val onPauseClick = stopWatch::pause
val onResetClick = stopWatch::reset
Window(
onCloseRequest = ::exitApplication,
onKeyEvent = {
if (it.isCtrlPressed && it.key == Key.A) {
println("Ctrl + A is pressed")
stopWatch.reset()
true
} else {
// let other handlers receive this event
false
}
}
) {
App(
stopWatch,
onStartClick,
onPauseClick,
onResetClick)
}
}
现在,再次启动秒表,然后让我们将 java 文件编辑为这样。
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
public class vomit
{
public static void main(final String[] args) throws AWTException
{
final Robot robot = new Robot();
System.out.println("Waiting");
robot.delay(5_000); //waits 5 seconds
System.out.println("Pressing CTRL+A");
robot.keyPress(KeyEvent.VK_CONTROL);
robot.keyPress(KeyEvent.VK_A);
robot.keyRelease(KeyEvent.VK_CONTROL);
robot.keyRelease(KeyEvent.VK_A);
}
}
好的,您的秒表应用程序已启动,您的 Java 代码已保存。现在,您自己手动按下秒表上的“开始”按钮。然后,执行新版本的 Java 代码,然后(快速!)切换回秒表应用程序。这次,机器人应该会自动按下 RESET 按钮。
这是使用键盘快捷键控制应用程序的方式。