Files
EVEngine/test/ui_control_semantics.cpp
T

186 lines
7.4 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "ui/UIHost.h"
#include "ui/UISystem.h"
#include "ui/Widget.h"
#include <string>
using namespace eve::ui;
namespace {
UIHost *createHost(const std::string &name) {
auto host = UIHost::resolve(UIHost::createHost(name));
return host ? &host->get() : nullptr;
}
UINode *node(UIHost &host, const std::string &id) {
auto found = host.findById(id);
return found ? &found->get() : nullptr;
}
} // namespace
TEST_CASE("ui.control.policies_survive_flatten_and_reconcile") {
UIHost *host = createHost("control-semantics");
REQUIRE(host != nullptr);
WidgetDesc action = button("Apply", "apply")
.withEnabled(false)
.withFocusMode(FocusMode::Click)
.withMouseFilter(MouseFilter::Ignore)
.withTabIndex(7)
.withFocusOrder("cancel", "reset")
.withFocusNeighbors("left", "right", "up", "down")
.withAccessibility(AccessibilityRole::Button, "Apply changes",
"Commits the current property edits");
host->setTree(window("Controls", {std::move(action)}, "root"));
UINode *applyNode = node(*host, "apply");
REQUIRE(applyNode != nullptr);
CHECK(!applyNode->enabled);
CHECK_EQ(static_cast<int>(applyNode->focusMode), static_cast<int>(FocusMode::Click));
CHECK_EQ(static_cast<int>(applyNode->mouseFilter), static_cast<int>(MouseFilter::Ignore));
CHECK_EQ(applyNode->tabIndex, 7);
CHECK_EQ(applyNode->focusPrevious, std::string("cancel"));
CHECK_EQ(applyNode->focusNext, std::string("reset"));
CHECK_EQ(applyNode->focusLeft, std::string("left"));
CHECK_EQ(applyNode->focusRight, std::string("right"));
CHECK_EQ(applyNode->focusUp, std::string("up"));
CHECK_EQ(applyNode->focusDown, std::string("down"));
CHECK_EQ(static_cast<int>(applyNode->accessibilityRole), static_cast<int>(AccessibilityRole::Button));
CHECK_EQ(applyNode->accessibilityName, std::string("Apply changes"));
WidgetDesc updated = button("Apply now", "apply")
.withFocusMode(FocusMode::All)
.withMouseFilter(MouseFilter::Pass)
.withTabIndex(2)
.withAccessibility(AccessibilityRole::Button, "Apply now");
CHECK(host->setTreeReconcile(window("Controls", {std::move(updated)}, "root")));
applyNode = node(*host, "apply");
REQUIRE(applyNode != nullptr);
CHECK(applyNode->enabled);
CHECK_EQ(static_cast<int>(applyNode->focusMode), static_cast<int>(FocusMode::All));
CHECK_EQ(static_cast<int>(applyNode->mouseFilter), static_cast<int>(MouseFilter::Pass));
CHECK_EQ(applyNode->tabIndex, 2);
CHECK(applyNode->focusPrevious.empty());
CHECK_EQ(applyNode->accessibilityName, std::string("Apply now"));
}
TEST_CASE("ui.control.focus_request_rejects_ineligible_controls") {
UIHost *host = createHost("focus-semantics");
REQUIRE(host != nullptr);
host->setTree(window("Focus",
{button("First", "first"),
button("Disabled", "disabled").withEnabled(false),
button("Decorative", "decorative").withFocusMode(FocusMode::None)},
"root"));
CHECK(!host->requestFocusById("missing"));
CHECK(!host->requestFocusById("disabled"));
CHECK(!host->requestFocusById("decorative"));
CHECK(!host->requestFocusById("root"));
CHECK(host->requestFocusById("first"));
CHECK(node(*host, "first")->focusRequested);
node(*host, "first")->focused = true;
CHECK_EQ(host->focusedId(), std::string("first"));
host->setEnabledById("first", false);
CHECK(host->focusedId().empty());
CHECK(!node(*host, "first")->focusRequested);
}
TEST_CASE("ui.control.focus_navigation_uses_neighbors_then_stable_tab_order") {
UIHost *host = createHost("focus-navigation");
REQUIRE(host != nullptr);
host->setTree(window(
"Focus",
{text("Label", "label"),
button("Click only", "click").withFocusMode(FocusMode::Click).withTabIndex(0),
button("Disabled", "disabled").withEnabled(false).withTabIndex(5),
button("Second", "second").withTabIndex(10),
button("First", "first")
.withTabIndex(20)
.withFocusOrder("", "special")
.withFocusNeighbors("second", "special", "second", "special"),
button("Special", "special").withTabIndex(30).withFocusOrder("second", "")},
"root"));
// No current focus: select the lowest eligible tab index. Decorative,
// disabled and click-only nodes do not participate in keyboard traversal.
CHECK(host->moveFocus(FocusDirection::Next));
CHECK(node(*host, "second")->focusRequested);
node(*host, "second")->focused = true;
CHECK(host->moveFocus(FocusDirection::Next));
CHECK(node(*host, "first")->focusRequested);
node(*host, "second")->focused = false;
node(*host, "first")->focused = true;
CHECK(host->moveFocus(FocusDirection::Next));
CHECK(node(*host, "special")->focusRequested);
node(*host, "first")->focused = false;
node(*host, "special")->focused = true;
CHECK(host->moveFocus(FocusDirection::Previous));
CHECK(node(*host, "second")->focusRequested);
node(*host, "special")->focused = false;
node(*host, "first")->focused = true;
CHECK(host->moveFocus(FocusDirection::Left));
CHECK(node(*host, "second")->focusRequested);
node(*host, "first")->focused = false;
node(*host, "special")->focused = true;
CHECK(!host->moveFocus(FocusDirection::Next, false));
}
TEST_CASE("ui.control.mouse_pass_bubbles_click_and_stop_contains_it") {
int childClicks = 0;
int ignoredClicks = 0;
int rootClicks = 0;
UIHost *host = createHost("pointer-routing");
REQUIRE(host != nullptr);
WidgetDesc child = button("Action", "action", [&]() { ++childClicks; })
.withMouseFilter(MouseFilter::Pass);
WidgetDesc ignored = group({std::move(child)}, "ignored")
.withClick([&]() { ++ignoredClicks; })
.withMouseFilter(MouseFilter::Ignore);
WidgetDesc root = window("Routing", {std::move(ignored)}, "root")
.withClick([&]() { ++rootClicks; })
.withMouseFilter(MouseFilter::Stop);
host->setTree(std::move(root));
UINode *action = node(*host, "action");
REQUIRE(action != nullptr);
auto tree = host->tree();
REQUIRE(!tree->nodes.empty());
CHECK_EQ(tree->nodes[size_t(action->parent)].id, std::string("ignored"));
UISystem::pendingEvents().clear();
UISystem::clickQueue().clear();
UIEvent event;
event.host = host->handle();
event.hostName = host->getName();
event.nodeId = action->id;
event.nodeIndex = int(action - tree->nodes.data());
event.kind = "click";
event.handlerIndex = action->handlerClick;
UISystem::pendingEvents().push_back(event);
UISystem::dispatchEvents();
CHECK_EQ(childClicks, 1);
CHECK_EQ(ignoredClicks, 0);
CHECK_EQ(rootClicks, 1);
CHECK_EQ(UISystem::consumeClickFor(host->getName()), std::string("action"));
action->mouseFilter = MouseFilter::Stop;
UISystem::pendingEvents().push_back(event);
UISystem::dispatchEvents();
CHECK_EQ(childClicks, 2);
CHECK_EQ(rootClicks, 1);
}