3个稳定版本

1.7.2 2024年8月14日
1.7.1 2024年7月25日
1.7.0 2024年7月18日

#741 in GUI

Download history 107/week @ 2024-07-17 116/week @ 2024-07-24 28/week @ 2024-07-31 59/week @ 2024-08-07

310 每月下载
用于 i-slint-backend-selector

GPL-3.0-only…

1.5MB
28K SLoC

注意:此库是 Slint项目 的内部crate。此crate不应直接由使用Slint的应用程序使用。您应使用 slint crate。

警告:此crate不遵循semver版本约定,并且只能与Cargo.toml中的 version = ""=x.y.z"" 一起使用。

Slint初步测试API

此crate提供了我们正在开发的初步API,以使Slint应用程序能够实现不同的用户界面(UI)测试场景。

要使用此功能,您需要在导入依赖项时谨慎,因为此crate不遵循semver,可能在任何补丁版本中引入破坏性更改。此外,此crate的版本必须与Slint的版本匹配。为了表明您特别想要此版本,请将 = 符号包含在版本字符串中。

[dependencies]
slint = { version = "x.y.z", ... }
i-slint-backend-testing = "=x.y.z"

测试后端

默认情况下,Slint应用程序将通过使用窗口系统(如果存在)或直接渲染到帧缓冲区,选择适合屏幕显示的应用程序后端和渲染器。

在无窗口系统/显示的CI环境中进行自动化测试时,仍然可能希望运行测试。Slint测试后端模拟了一个无需窗口系统的窗口系统:不渲染像素,并以固定字体大小测量文本。

使用 [init_integration_test_with_system_time()] 对需要 Slint 提供事件循环的集成测试进行操作,例如在创建线程并调用slint::invoke_from_event_loop()时。如果您想不仅模拟窗口系统,还要模拟系统时间,则使用 [init_integration_test_with_mock_time()] 来初始化后端,然后调用 [mock_elapsed_time()] 来推进动画并使计时器更接近下一次超时。

当您的测试代码不需要事件循环时,使用 [init_no_event_loop()] 对单元测试进行操作。请注意,在此场景中,系统时间也被模拟,因此使用 [mock_elapsed_time()] 来推进动画和计时器。

初步用户界面测试 API

我们正在开发 API,以方便为 Slint 基础的 UI 创建自动化测试。一个构建块是从一个常规应用程序过程中进行自我检查和修改状态的能力。

一个常规应用程序可能有一个如下的main()入口点

# slint::slint!{ export component App {} }
fn main() -> Result<(), slint::PlatformError>
{
    let app = App::new()?;
    // ... set up state, callbacks, models, ...

    app.run()
}

此外,可能还需要创建一个集成测试,以验证当模拟用户输入时应用程序的行为。本包提供的用户界面测试 API 的目标是方便定位、修改和验证 UI 中元素的状态。 ElementHandle 为这些元素提供视图。

以下示例假设在 UI 的某个地方您声明了一个带文本 "提交" 的 Button,并且您可能想验证在模拟激活时应用程序的行为。这是通过其可访问性接口进行的,每个 Button 都实现了。

import { Button } from "std-widgets.slint";
component Form {
    callback submit();
    VerticalLayout {
        // ...
        Button {
            text: "Submit";
            clicked => { root.submit(); }
        }
    }
}

export component App {
    callback submit <=> form.submit;
    // ...
    form := Form {
        // ...
    }
}
# use i_slint_core_macros::identity as test;
# slint::slint!{
#     import { Button } from "std-widgets.slint";
#     component Form {
#     callback submit();
#     VerticalLayout {
#         // ...
#         Button {
#             text: "Submit";
#             clicked => { root.submit(); }
#         }
#     }
# }
#
# export component App {
#     callback submit <=> form.submit;
#     // ...
#     form := Form {
#         // ...
#     }
# }
# }
#[test]
fn test_basic_user_interface()
{
    i_slint_backend_testing::init_no_event_loop();
    let app = App::new().unwrap();
    // ... set up state, callbacks, models, ...
    let submitted = std::rc::Rc::new(std::cell::RefCell::new(false));

    app.on_submit({
        let submitted = submitted.clone();
        move || { *submitted.borrow_mut() = true; }
    });

    let buttons: Vec<_> = i_slint_backend_testing::ElementHandle::find_by_accessible_label(&app, "Submit").collect();
    assert_eq!(buttons.len(), 1);
    let button = &buttons[0];

    button.invoke_accessible_default_action();

    assert!(*submitted.borrow());
}

模拟事件/异步测试

在测试用户界面时,不仅可能需要在元素上调用可访问的操作,还可能需要模拟触摸或鼠标输入。例如,按钮上的鼠标点击是一个序列

  1. 一个初始鼠标移动事件到按钮的位置
  2. 鼠标按下事件
  3. 在现实生活中,一定量的时间会过去。
  4. 最后,用户再次抬起手指,触发鼠标释放事件。

为了模拟这种行为,ElementHandle 提供了相应的函数。例如,ElementHandle::click()ElementHandle::hover() 可以用来模拟点击和悬停事件。

调用这些函数需要在后台运行测试函数本身作为一个future,并运行一个事件循环。这可以通过使用slint::spawn_local()slint::run_event_loop()slint::quit_event_loop()来完成。以下示例将测试的核心函数包装在一个异步闭包中


use slint::platform::PointerEventButton;

#[test]
fn test_click() {
    i_slint_backend_testing::init_integration_test_with_system_time();

    slint::spawn_local(async move {
        slint::slint! {
            export component App inherits Window {
                out property <int> click-count: 0;
                ta := TouchArea {
                    clicked => { root.click-count += 1; }
                }
            }
        }

        let app = App::new().unwrap();

        let mut it = ElementHandle::find_by_element_id(&app, "App::ta");
        let elem = it.next().unwrap();
        assert!(it.next().is_none());

        assert_eq!(app.get_click_count(), 0);
        elem.single_click(PointerEventButton::Left).await;
        assert_eq!(app.get_click_count(), 1);

        slint::quit_event_loop().unwrap();
    })
    .unwrap();
    slint::run_event_loop().unwrap();
}

在初始化支持使用系统时间的测试后端后,创建一个异步闭包来进行实际测试。在随后的run_event_loop()调用中,启动事件循环,这将开始轮询传递给spawn_local()的异步闭包。

在这个闭包中,我们现在可以调用future [ElementHandle::single_click()] 返回的 .await,这将保持事件循环运行,直到点击完成,然后继续执行测试函数。

依赖项

~5–17MB
~218K SLoC