Testing: Change ElementReference to be weak

We don't want testing variables to affect the life-cycle of elements in the UI.
This commit is contained in:
Simon Hausmann 2024-04-23 16:37:36 +02:00 committed by Simon Hausmann
parent cca570549c
commit d8c1096149
5 changed files with 153 additions and 65 deletions

View file

@ -22,7 +22,7 @@ inline void init()
/// Use find_by_accessible_label() to obtain all elements matching the given accessible label.
class ElementHandle
{
cbindgen_private::ItemRc inner;
cbindgen_private::ItemWeak inner;
public:
/// Find all elements matching the given accessible label.
@ -38,41 +38,47 @@ public:
SharedVector<ElementHandle> result;
cbindgen_private::slint_testing_element_find_by_accessible_label(
&vrc, &label_view,
reinterpret_cast<SharedVector<cbindgen_private::ItemRc> *>(&result));
reinterpret_cast<SharedVector<cbindgen_private::ItemWeak> *>(&result));
return result;
}
/// Returns true if the underlying element still exists; false otherwise.
bool is_valid() const { return private_api::upgrade_item_weak(inner).has_value(); }
/// Returns the accessible-label of that element, if any.
std::optional<SharedString> accessible_label() const
{
if (auto item = private_api::upgrade_item_weak(inner)) {
SharedString result;
if (inner.item_tree.vtable()->accessible_string_property(
inner.item_tree.borrow(), inner.index,
if (item->item_tree.vtable()->accessible_string_property(
item->item_tree.borrow(), item->index,
cbindgen_private::AccessibleStringProperty::Label, &result)) {
return result;
} else {
return std::nullopt;
}
}
return std::nullopt;
}
/// Returns the accessible-value of that element, if any.
std::optional<SharedString> accessible_value() const
{
if (auto item = private_api::upgrade_item_weak(inner)) {
SharedString result;
if (inner.item_tree.vtable()->accessible_string_property(
inner.item_tree.borrow(), inner.index,
if (item->item_tree.vtable()->accessible_string_property(
item->item_tree.borrow(), item->index,
cbindgen_private::AccessibleStringProperty::Value, &result)) {
return result;
} else {
return std::nullopt;
}
}
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
{
if (auto item = private_api::upgrade_item_weak(inner)) {
union SetValueHelper {
cbindgen_private::AccessibilityAction action;
SetValueHelper(SharedString value)
@ -86,13 +92,16 @@ public:
~SetValueHelper() { action.set_value.~SetValue_Body(); }
} action(std::move(value));
inner.item_tree.vtable()->accessibility_action(inner.item_tree.borrow(), inner.index,
item->item_tree.vtable()->accessibility_action(item->item_tree.borrow(), item->index,
&action.action);
}
}
/// Invokes the default accessibility action of that element (`accessible-action-default`).
/// Invokes the default accessibility action of that element
/// (`accessible-action-default`).
void invoke_default_action() const
{
if (auto item = private_api::upgrade_item_weak(inner)) {
union DefaultActionHelper {
cbindgen_private::AccessibilityAction action;
DefaultActionHelper()
@ -103,26 +112,35 @@ public:
~DefaultActionHelper() { }
} action;
inner.item_tree.vtable()->accessibility_action(inner.item_tree.borrow(), inner.index,
item->item_tree.vtable()->accessibility_action(item->item_tree.borrow(), item->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);
if (auto item = private_api::upgrade_item_weak(inner)) {
auto rect =
item->item_tree.vtable()->item_geometry(item->item_tree.borrow(), item->index);
return LogicalSize({ rect.width, rect.height });
}
return LogicalSize({ 0, 0 });
}
/// Returns the absolute position of this element
LogicalPosition absolute_position() const
{
if (auto item = private_api::upgrade_item_weak(inner)) {
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);
item->item_tree.vtable()->item_geometry(item->item_tree.borrow(), item->index);
cbindgen_private::LogicalPoint abs =
slint::cbindgen_private::slint_item_absolute_position(&item->item_tree,
item->index);
return LogicalPosition({ abs.x + rect.x, abs.y + rect.y });
}
return LogicalPosition({ 0, 0 });
}
};
}

View file

@ -114,6 +114,16 @@ inline vtable::Layout drop_in_place(ItemTreeRef item_tree)
# endif
#endif // !defined(DOXYGEN)
inline std::optional<cbindgen_private::ItemRc>
upgrade_item_weak(const cbindgen_private::ItemWeak &item_weak)
{
if (auto item_tree_strong = item_weak.item_tree.lock()) {
return { { *item_tree_strong, item_weak.index } };
} else {
return std::nullopt;
}
}
} // namespace private_api
template<typename T>

View file

@ -2,8 +2,7 @@
// 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::item_tree::{ItemTreeRc, ItemWeak};
use i_slint_core::slice::Slice;
use i_slint_core::SharedVector;
@ -16,7 +15,7 @@ pub extern "C" fn slint_testing_init_backend() {
pub extern "C" fn slint_testing_element_find_by_accessible_label(
root: &ItemTreeRc,
label: &Slice<u8>,
out: &mut SharedVector<ItemRc>,
out: &mut SharedVector<ItemWeak>,
) {
let Ok(label) = core::str::from_utf8(label.as_slice()) else { return };
*out = crate::search_api::search_item(root, |item| {

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial
use i_slint_core::accessibility::{AccessibilityAction, AccessibleStringProperty};
use i_slint_core::item_tree::{ItemTreeRc, ItemVisitorResult, TraversalOrder};
use i_slint_core::item_tree::{ItemTreeRc, ItemVisitorResult, ItemWeak, TraversalOrder};
use i_slint_core::items::ItemRc;
use i_slint_core::window::WindowInner;
use i_slint_core::{SharedString, SharedVector};
@ -10,7 +10,7 @@ use i_slint_core::{SharedString, SharedVector};
pub(crate) fn search_item(
item_tree: &ItemTreeRc,
mut filter: impl FnMut(&ItemRc) -> bool,
) -> SharedVector<ItemRc> {
) -> SharedVector<ItemWeak> {
let mut result = SharedVector::default();
i_slint_core::item_tree::visit_items(
item_tree,
@ -18,7 +18,7 @@ pub(crate) fn search_item(
|parent_tree, _, index, _| {
let item_rc = ItemRc::new(parent_tree.clone(), index);
if filter(&item_rc) {
result.push(item_rc);
result.push(item_rc.downgrade());
}
ItemVisitorResult::Continue(())
},
@ -27,9 +27,13 @@ pub(crate) fn search_item(
result
}
pub struct ElementHandle(ItemRc);
pub struct ElementHandle(ItemWeak);
impl ElementHandle {
pub fn is_valid(&self) -> bool {
self.0.upgrade().is_some()
}
pub fn find_by_accessible_label(
component: &impl i_slint_core::api::ComponentHandle,
label: &str,
@ -44,29 +48,47 @@ impl ElementHandle {
}
pub fn invoke_default_action(&self) {
self.0.accessible_action(&AccessibilityAction::Default)
if let Some(item) = self.0.upgrade() {
item.accessible_action(&AccessibilityAction::Default)
}
}
pub fn accessible_value(&self) -> Option<SharedString> {
self.0.accessible_string_property(AccessibleStringProperty::Value)
self.0
.upgrade()
.and_then(|item| item.accessible_string_property(AccessibleStringProperty::Value))
}
pub fn set_accessible_value(&self, value: SharedString) {
self.0.accessible_action(&AccessibilityAction::SetValue(value))
if let Some(item) = self.0.upgrade() {
item.accessible_action(&AccessibilityAction::SetValue(value))
}
}
pub fn accessible_label(&self) -> Option<SharedString> {
self.0.accessible_string_property(AccessibleStringProperty::Label)
self.0
.upgrade()
.and_then(|item| item.accessible_string_property(AccessibleStringProperty::Label))
}
pub fn size(&self) -> i_slint_core::api::LogicalSize {
let g = self.0.geometry();
self.0
.upgrade()
.map(|item| {
let g = item.geometry();
i_slint_core::lengths::logical_size_to_api(g.size)
})
.unwrap_or_default()
}
pub fn absolute_position(&self) -> i_slint_core::api::LogicalPosition {
let g = self.0.geometry();
let p = self.0.map_to_window(g.origin);
self.0
.upgrade()
.map(|item| {
let g = item.geometry();
let p = item.map_to_window(g.origin);
i_slint_core::lengths::logical_position_to_api(p)
})
.unwrap_or_default()
}
}

View file

@ -0,0 +1,39 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial
export component TestCase {
in-out property <bool> condition: true;
if condition: Text {
accessible-role: text;
accessible-label: "testlabel";
text: "Ok";
}
}
/*
```rust
let instance = TestCase::new().unwrap();
let mut label_search = slint_testing::ElementHandle::find_by_accessible_label(&instance, "testlabel");
let label = label_search.next().unwrap();
assert!(label.is_valid());
instance.set_condition(false);
// Trigger re-evaluation of the item tree traversal and thus re-evaluation of the repeater
slint_testing::send_mouse_click(&instance, 5., 5.);
assert!(!label.is_valid());
```
```cpp
auto handle = TestCase::create();
const TestCase &instance = *handle;
auto label_search = slint::testing::ElementHandle::find_by_accessible_label(handle, "testlabel");
assert_eq(label_search.size(), 1);
auto label = label_search[0];
assert(label.is_valid());
instance.set_condition(false);
// Trigger re-evaluation of the item tree traversal and thus re-evaluation of the repeater
slint_testing::send_mouse_click(&instance, 5., 5.);
assert(!label.is_valid());
```
*/