mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
C++ testing API: Intreoduce the ElementHandle
This commit is contained in:
parent
13fe59cc2e
commit
475ced0a62
15 changed files with 225 additions and 25 deletions
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
|
@ -342,7 +342,7 @@ jobs:
|
|||
with:
|
||||
crate: cross
|
||||
- name: Check
|
||||
run: cross check --target=armv7-unknown-linux-gnueabihf -p slint-cpp --no-default-features --features=internal-testing,interpreter,std
|
||||
run: cross check --target=armv7-unknown-linux-gnueabihf -p slint-cpp --no-default-features --features=testing,interpreter,std
|
||||
|
||||
uefi-demo:
|
||||
env:
|
||||
|
|
|
@ -104,6 +104,7 @@ define_cargo_dependent_feature(backend-linuxkms-noseat "Enable support for the b
|
|||
|
||||
define_cargo_dependent_feature(gettext "Enable support of translations using gettext" OFF "NOT SLINT_FEATURE_FREESTANDING")
|
||||
define_cargo_dependent_feature(accessibility "Enable integration with operating system provided accessibility APIs" ON "NOT SLINT_FEATURE_FREESTANDING")
|
||||
define_cargo_dependent_feature(testing "Enable support for testing API (experimental)" ON "NOT SLINT_FEATURE_FREESTANDING")
|
||||
define_cargo_feature(experimental "Enable experimental features. (No backward compatibility guarantees)" OFF)
|
||||
|
||||
if (SLINT_BUILD_RUNTIME)
|
||||
|
|
|
@ -27,7 +27,7 @@ name = "slint_cpp"
|
|||
[features]
|
||||
interpreter = ["slint-interpreter", "std"]
|
||||
# Enable some function used by the integration tests
|
||||
internal-testing = ["i-slint-backend-testing"]
|
||||
testing = ["dep:i-slint-backend-testing"]
|
||||
|
||||
backend-qt = ["i-slint-backend-selector/backend-qt", "std"]
|
||||
backend-winit = ["i-slint-backend-selector/backend-winit", "std"]
|
||||
|
@ -51,7 +51,7 @@ default = ["std", "backend-winit", "renderer-femtovg", "backend-qt"]
|
|||
|
||||
[dependencies]
|
||||
i-slint-backend-selector = { workspace = true, optional = true }
|
||||
i-slint-backend-testing = { workspace = true, optional = true }
|
||||
i-slint-backend-testing = { workspace = true, optional = true, features = ["ffi"] }
|
||||
i-slint-renderer-skia = { workspace = true, features = ["default", "x11", "wayland"], optional = true }
|
||||
i-slint-core = { workspace = true, features = ["ffi"] }
|
||||
slint-interpreter = { workspace = true, features = ["ffi", "compat-1-2"], optional = true }
|
||||
|
|
|
@ -805,6 +805,29 @@ fn gen_backend_qt(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_testing(
|
||||
root_dir: &Path,
|
||||
include_dir: &Path,
|
||||
dependencies: &mut Vec<PathBuf>,
|
||||
) -> anyhow::Result<()> {
|
||||
let config = default_config();
|
||||
|
||||
let mut crate_dir = root_dir.to_owned();
|
||||
crate_dir.extend(["internal", "backends", "testing"].iter());
|
||||
|
||||
ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?;
|
||||
|
||||
cbindgen::Builder::new()
|
||||
.with_config(config)
|
||||
.with_crate(crate_dir)
|
||||
.with_include("slint_testing_internal.h")
|
||||
.generate()
|
||||
.context("Unable to generate bindings for slint_testing_internal.h")?
|
||||
.write_to_file(include_dir.join("slint_testing_internal.h"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_platform(
|
||||
root_dir: &Path,
|
||||
include_dir: &Path,
|
||||
|
@ -933,7 +956,7 @@ macro_rules! declare_features {
|
|||
};
|
||||
}
|
||||
|
||||
declare_features! {interpreter backend_qt freestanding renderer_software renderer_skia experimental gettext}
|
||||
declare_features! {interpreter backend_qt freestanding renderer_software renderer_skia experimental gettext testing}
|
||||
|
||||
/// Generate the headers.
|
||||
/// `root_dir` is the root directory of the slint git repo
|
||||
|
@ -953,6 +976,9 @@ pub fn gen_all(
|
|||
gen_corelib(root_dir, include_dir, &mut deps, enabled_features)?;
|
||||
gen_backend_qt(root_dir, include_dir, &mut deps)?;
|
||||
gen_platform(root_dir, include_dir, &mut deps)?;
|
||||
if enabled_features.testing {
|
||||
gen_testing(root_dir, include_dir, &mut deps)?;
|
||||
}
|
||||
if enabled_features.interpreter {
|
||||
gen_interpreter(root_dir, include_dir, &mut deps)?;
|
||||
}
|
||||
|
|
130
api/cpp/include/slint-testing.h
Normal file
130
api/cpp/include/slint-testing.h
Normal file
|
@ -0,0 +1,130 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial
|
||||
|
||||
#include "slint.h"
|
||||
#include "slint_testing_internal.h"
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#ifdef SLINT_FEATURE_TESTING
|
||||
|
||||
namespace slint::testing {
|
||||
/// Init the testing backend.
|
||||
/// Should be called before any other Slint function that can access the platform.
|
||||
/// Then future windows will not appear on the screen anymore
|
||||
inline void init()
|
||||
{
|
||||
cbindgen_private::slint_testing_init_backend();
|
||||
}
|
||||
|
||||
/// A Handle to an element to query accessible property for testing purposes.
|
||||
///
|
||||
/// Use find_by_accessible_label() to obtain all elements matching the given accessible label.
|
||||
class ElementHandle
|
||||
{
|
||||
cbindgen_private::ItemRc inner;
|
||||
|
||||
public:
|
||||
/// Find all elements matching the given accessible label.
|
||||
template<typename T>
|
||||
static SharedVector<ElementHandle> find_by_accessible_label(const ComponentHandle<T> &component,
|
||||
std::string_view label)
|
||||
{
|
||||
cbindgen_private::Slice<uint8_t> label_view {
|
||||
const_cast<unsigned char *>(reinterpret_cast<const unsigned char *>(label.data())),
|
||||
label.size()
|
||||
};
|
||||
auto vrc = component.into_dyn();
|
||||
SharedVector<ElementHandle> result;
|
||||
cbindgen_private::slint_testing_element_find_by_accessible_label(
|
||||
&vrc, &label_view,
|
||||
reinterpret_cast<SharedVector<cbindgen_private::ItemRc> *>(&result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns the accessible-label of that element, if any.
|
||||
std::optional<SharedString> accessible_label() const
|
||||
{
|
||||
SharedString result;
|
||||
if (inner.item_tree.vtable()->accessible_string_property(
|
||||
inner.item_tree.borrow(), inner.index,
|
||||
cbindgen_private::AccessibleStringProperty::Label, &result)) {
|
||||
return result;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the accessible-value of that element, if any.
|
||||
std::optional<SharedString> accessible_value() const
|
||||
{
|
||||
SharedString result;
|
||||
if (inner.item_tree.vtable()->accessible_string_property(
|
||||
inner.item_tree.borrow(), inner.index,
|
||||
cbindgen_private::AccessibleStringProperty::Value, &result)) {
|
||||
return result;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the accessible-value of that element.
|
||||
///
|
||||
/// Setting the value will invoke the `accessible-action-set-value` callback.
|
||||
void set_accessible_value(SharedString value) const
|
||||
{
|
||||
union SetValueHelper {
|
||||
cbindgen_private::AccessibilityAction action;
|
||||
SetValueHelper(SharedString value)
|
||||
// : action { .set_value = { cbindgen_private::AccessibilityAction::Tag::SetValue,
|
||||
// std::move(value) } }
|
||||
{
|
||||
new (&action.set_value) cbindgen_private::AccessibilityAction::SetValue_Body {
|
||||
cbindgen_private::AccessibilityAction::Tag::SetValue, std::move(value)
|
||||
};
|
||||
}
|
||||
~SetValueHelper() { action.set_value.~SetValue_Body(); }
|
||||
|
||||
} action(std::move(value));
|
||||
inner.item_tree.vtable()->accessibility_action(inner.item_tree.borrow(), inner.index,
|
||||
&action.action);
|
||||
}
|
||||
|
||||
/// Invokes the default accessibility action of that element (`accessible-action-default`).
|
||||
void invoke_default_action() const
|
||||
{
|
||||
union DefaultActionHelper {
|
||||
cbindgen_private::AccessibilityAction action;
|
||||
DefaultActionHelper()
|
||||
//: action { .tag = cbindgen_private::AccessibilityAction::Tag::Default }
|
||||
{
|
||||
action.tag = cbindgen_private::AccessibilityAction::Tag::Default;
|
||||
}
|
||||
~DefaultActionHelper() { }
|
||||
|
||||
} action;
|
||||
inner.item_tree.vtable()->accessibility_action(inner.item_tree.borrow(), inner.index,
|
||||
&action.action);
|
||||
}
|
||||
|
||||
/// Returns the size of this element
|
||||
LogicalSize size() const
|
||||
{
|
||||
auto rect = inner.item_tree.vtable()->item_geometry(inner.item_tree.borrow(), inner.index);
|
||||
return LogicalSize({ rect.width, rect.height });
|
||||
}
|
||||
|
||||
/// Returns the absolute position of this element
|
||||
LogicalPosition absolute_position() const
|
||||
{
|
||||
cbindgen_private::LogicalRect rect =
|
||||
inner.item_tree.vtable()->item_geometry(inner.item_tree.borrow(), inner.index);
|
||||
cbindgen_private::LogicalPoint abs = slint::cbindgen_private::slint_item_absolute_position(
|
||||
&inner.item_tree, inner.index);
|
||||
return LogicalPosition({ abs.x + rect.x, abs.y + rect.y });
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SLINT_FEATURE_TESTING
|
|
@ -2,19 +2,13 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial
|
||||
|
||||
#pragma once
|
||||
#include "slint.h"
|
||||
#include <concepts>
|
||||
#include "slint-testing.h"
|
||||
#include <iostream>
|
||||
|
||||
// this file contains function useful for internal testing
|
||||
|
||||
namespace slint::private_api::testing {
|
||||
|
||||
inline void init()
|
||||
{
|
||||
cbindgen_private::slint_testing_init_backend();
|
||||
}
|
||||
|
||||
inline void mock_elapsed_time(int64_t time_in_ms)
|
||||
{
|
||||
cbindgen_private::slint_mock_elapsed_time(time_in_ms);
|
||||
|
|
|
@ -24,8 +24,10 @@ pub fn with_platform<R>(
|
|||
i_slint_core::with_platform(|| Err(i_slint_core::platform::PlatformError::NoPlatform), f)
|
||||
}
|
||||
|
||||
/// One need to make sure something from the crate is exported,
|
||||
/// otherwise its symbols are not going to be in the final binary
|
||||
// One need to make sure something from the crate is exported,
|
||||
// otherwise its symbols are not going to be in the final binary
|
||||
#[cfg(feature = "testing")]
|
||||
pub use i_slint_backend_testing;
|
||||
#[cfg(feature = "slint-interpreter")]
|
||||
pub use slint_interpreter;
|
||||
|
||||
|
@ -138,12 +140,6 @@ pub unsafe extern "C" fn slint_register_bitmap_font(
|
|||
window_adapter.renderer().register_bitmap_font(font_data);
|
||||
}
|
||||
|
||||
#[cfg(feature = "internal-testing")]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn slint_testing_init_backend() {
|
||||
i_slint_backend_testing::init_no_event_loop();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod allocator {
|
||||
use core::alloc::Layout;
|
||||
|
|
|
@ -19,6 +19,8 @@ path = "lib.rs"
|
|||
[features]
|
||||
# Internal feature that is only enabled for Slint's own tests
|
||||
internal = []
|
||||
# ffi for C++ bindings
|
||||
ffi = []
|
||||
|
||||
[dependencies]
|
||||
i-slint-core = { workspace = true }
|
||||
|
|
25
internal/backends/testing/ffi.rs
Normal file
25
internal/backends/testing/ffi.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial
|
||||
|
||||
use i_slint_core::accessibility::AccessibleStringProperty;
|
||||
use i_slint_core::item_tree::ItemTreeRc;
|
||||
use i_slint_core::items::ItemRc;
|
||||
use i_slint_core::slice::Slice;
|
||||
use i_slint_core::SharedVector;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn slint_testing_init_backend() {
|
||||
crate::init_integration_test();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn slint_testing_element_find_by_accessible_label(
|
||||
root: &ItemTreeRc,
|
||||
label: &Slice<u8>,
|
||||
out: &mut SharedVector<ItemRc>,
|
||||
) {
|
||||
let Ok(label) = core::str::from_utf8(label.as_slice()) else { return };
|
||||
*out = crate::search_api::search_item(root, |item| {
|
||||
item.accessible_string_property(AccessibleStringProperty::Label).is_some_and(|x| x == label)
|
||||
})
|
||||
}
|
|
@ -13,6 +13,8 @@ pub use internal_tests::*;
|
|||
mod testing_backend;
|
||||
#[cfg(feature = "internal")]
|
||||
pub use testing_backend::*;
|
||||
#[cfg(feature = "ffi")]
|
||||
mod ffi;
|
||||
|
||||
/// Initialize the testing backend without support for event loop.
|
||||
/// This means that each test thread can use its own backend, but global functions that needs
|
||||
|
|
|
@ -5,10 +5,13 @@ use i_slint_core::accessibility::{AccessibilityAction, AccessibleStringProperty}
|
|||
use i_slint_core::item_tree::{ItemTreeRc, ItemVisitorResult, TraversalOrder};
|
||||
use i_slint_core::items::ItemRc;
|
||||
use i_slint_core::window::WindowInner;
|
||||
use i_slint_core::SharedString;
|
||||
use i_slint_core::{SharedString, SharedVector};
|
||||
|
||||
fn search_item(item_tree: &ItemTreeRc, mut filter: impl FnMut(&ItemRc) -> bool) -> Vec<ItemRc> {
|
||||
let mut result = vec![];
|
||||
pub(crate) fn search_item(
|
||||
item_tree: &ItemTreeRc,
|
||||
mut filter: impl FnMut(&ItemRc) -> bool,
|
||||
) -> SharedVector<ItemRc> {
|
||||
let mut result = SharedVector::default();
|
||||
i_slint_core::item_tree::visit_items(
|
||||
item_tree,
|
||||
TraversalOrder::BackToFront,
|
||||
|
|
|
@ -46,7 +46,27 @@ assert_eq!(button.absolute_position(), slint::LogicalPosition::new(123., 143.));
|
|||
assert_eq!(button.size(), slint::LogicalSize::new(143., 76.));
|
||||
|
||||
assert_eq!(button.accessible_value().unwrap(), "0");
|
||||
button.set_accessible_value("45".into());
|
||||
instance.invoke_call();
|
||||
assert_eq!(button.accessible_value().unwrap(), "45");
|
||||
button.set_accessible_value("78".into());
|
||||
assert_eq!(button.accessible_value().unwrap(), "78");
|
||||
```
|
||||
|
||||
```cpp
|
||||
auto handle = TestCase::create();
|
||||
const TestCase &instance = *handle;
|
||||
|
||||
auto button_search = slint::testing::ElementHandle::find_by_accessible_label(handle, "Hello");
|
||||
assert_eq(button_search.size(), 1);
|
||||
auto button = button_search[0];
|
||||
|
||||
assert(button.absolute_position() == slint::LogicalPosition({123., 143.}));
|
||||
assert(button.size() == slint::LogicalSize({143., 76.}));
|
||||
|
||||
assert_eq(button.accessible_value().value(), "0");
|
||||
instance.invoke_call();
|
||||
assert_eq(button.accessible_value().value(), "45");
|
||||
button.set_accessible_value("78");
|
||||
assert_eq(button.accessible_value().value(), "78");
|
||||
```
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,7 @@ path = "main.rs"
|
|||
name = "test-driver-cpp"
|
||||
|
||||
[dependencies]
|
||||
slint-cpp = { workspace = true, features = ["internal-testing", "std"] }
|
||||
slint-cpp = { workspace = true, features = ["testing", "std"] }
|
||||
|
||||
[dev-dependencies]
|
||||
i-slint-compiler = { workspace = true, features = ["default", "cpp", "display-diagnostics"] }
|
||||
|
|
|
@ -55,7 +55,7 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
|
|||
namespace slint_testing = slint::private_api::testing;
|
||||
",
|
||||
)?;
|
||||
generated_cpp.write_all(b"int main() {\n slint::private_api::testing::init();\n")?;
|
||||
generated_cpp.write_all(b"int main() {\n slint::testing::init();\n")?;
|
||||
for x in test_driver_lib::extract_test_functions(&source).filter(|x| x.language_id == "cpp") {
|
||||
write!(generated_cpp, " {{\n {}\n }}\n", x.source.replace("\n", "\n "))?;
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ pub fn generate(show_warnings: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|||
renderer_skia: true,
|
||||
experimental: false,
|
||||
gettext: true,
|
||||
testing: true,
|
||||
};
|
||||
cbindgen::gen_all(&root, &generated_headers_dir, enabled_features)?;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue