#include "zeroerr/assert.h" #include "zeroerr/unittest.h" #include "ui/Component.h" #include "ui/Theme.h" #include "ui/UI.h" #include "ui/UIHost.h" #include "ui/UISystem.h" #include "ui/Widget.h" #include #include using namespace eve::ui; class InventoryPanel : public Component { public: std::vector items{"Sword", "Potion"}; int gold = 10; bool showExtra = true; WidgetDesc build() override { return window("Inventory", { text("Gold " + std::to_string(gold), "gold"), separator("sep"), listButtons("items", items), when(showExtra, text("Extra tip", "tip")), checkbox("Show tip", showExtra, "tipchk", [this](bool v) { showExtra = v; setState(); }), }, "root"); } }; TEST_CASE("UI.b.listAndWhen") { auto tree = window("W", { when(false, text("hidden", "h")), when(true, text("shown", "s")), listButtons("L", {"a", "b", "c"}), }); UIHost *h = UIHost::createHost("listwhen"); h->setTree(std::move(tree)); CHECK(h->findById("s") != nullptr); CHECK(h->findById("h") == nullptr); // when(false) → empty group, no "h" CHECK(h->findById("L/0") != nullptr); CHECK(h->findById("L/2") != nullptr); } TEST_CASE("UI.b.keyReconcilePropsOnly") { UIHost *h = UIHost::createHost("rec"); h->setTree(window("W", {text("v1", "label"), button("Go", "btn")}, "root")); bool rebuilt = h->setTreeReconcile(window("W", {text("v2", "label"), button("Go", "btn")}, "root")); CHECK(!rebuilt); // structure same → props patch CHECK(h->findById("label")->text == "v2"); rebuilt = h->setTreeReconcile( window("W", {text("v3", "label"), button("Go", "btn"), button("New", "n")}, "root")); CHECK(rebuilt); // child count changed → full rebuild CHECK(h->findById("n") != nullptr); } TEST_CASE("UI.b.componentRebuild") { InventoryPanel panel; panel.mountAs("inv"); UIHost *h = UISystem::findHost("inv"); REQUIRE(h != nullptr); CHECK(h->findById("gold")->text == "Gold 10"); CHECK(h->findById("items/0") != nullptr); panel.gold = 9; panel.items.push_back("Shield"); panel.markDirty(); CHECK(panel.updateIfDirty()); CHECK(h->findById("gold")->text == "Gold 9"); CHECK(h->findById("items/2") != nullptr); } TEST_CASE("UI.b.themeAndCheckbox") { UI *ui = UI::create(); ui->setThemeLight(); CHECK(globalTheme().windowBg[0] > 0.5f); CHECK(ui->getTheme() == "light"); // Light/dark share geometry so switching palette keeps a unified look. CHECK(globalTheme().frameRounding == Theme::dark().frameRounding); CHECK(globalTheme().windowPaddingX == Theme::dark().windowPaddingX); CHECK(globalTheme().fontScale == Theme::dark().fontScale); ui->setThemeDark(); CHECK(ui->getTheme() == "dark"); ui->setNavKeyboard(true); CHECK(globalTheme().navEnableKeyboard); CHECK(ui->setTheme("LIGHT")); CHECK(ui->getTheme() == "light"); CHECK(globalTheme().windowBg[0] > 0.5f); CHECK(!ui->setTheme("unknown")); CHECK(ui->getTheme() == "light"); // unchanged on failure ui->mountAs("chk", window("C", {checkbox("Mute", false, "mute")}, "root")); CHECK(ui->current()->findById("mute") != nullptr); CHECK(!ui->current()->findById("mute")->checked); ui->setChecked("mute", true); CHECK(ui->current()->findById("mute")->checked); } TEST_CASE("UI.b.themeTokensUnified") { Theme dark = Theme::dark(); Theme light = Theme::light(); CHECK(dark.frameRounding == light.frameRounding); CHECK(dark.windowRounding == light.windowRounding); CHECK(dark.windowBorderSize == light.windowBorderSize); CHECK(dark.frameBorderSize == light.frameBorderSize); CHECK(dark.itemSpacingX == light.itemSpacingX); CHECK(dark.windowPaddingY == light.windowPaddingY); CHECK(dark.fontScale == light.fontScale); // Palettes differ CHECK(dark.windowBg[0] < 0.5f); CHECK(light.windowBg[0] > 0.5f); CHECK(dark.text[0] > light.text[0]); } TEST_CASE("UI.b.scriptListBuilder") { UI *ui = UI::create(); ui->beginBuild(); ui->beginWindow("Shop", "root"); ui->beginList("goods"); ui->addListItem("Apple", "goods/0"); ui->addListItem("Bread", "goods/1"); ui->end(); ui->addSeparator("s"); ui->addCheckbox("Member", false, "mem"); ui->end(); CHECK(ui->mountBuildAs("shop")); CHECK(ui->current()->findById("goods/1") != nullptr); CHECK(ui->current()->findById("mem") != nullptr); }