mirror of
https://github.com/aydencharles/onionHEN.git
synced 2026-10-22 11:53:07 -05:00
216 lines
7.7 KiB
C++
216 lines
7.7 KiB
C++
#include "test_harness.h"
|
|
#include "settings_page_update.hpp"
|
|
#include "plugin_sprx_pages.hpp"
|
|
#include "shellui_state.hpp"
|
|
#include "toolbox_i18n.hpp"
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace onion::shellui::settings;
|
|
using ps5ui::Node;
|
|
|
|
class FakePage final : public PageView {
|
|
public:
|
|
explicit FakePage(const Node &root) : items(root.children), title(root.attrs.title) {}
|
|
std::vector<Node> items;
|
|
std::string title;
|
|
std::vector<std::string> calls;
|
|
int fail_at = -1;
|
|
int attempts = 0;
|
|
|
|
bool set_title(const std::string &value) override {
|
|
if (fail()) return false;
|
|
title = value;
|
|
calls.push_back("title");
|
|
return true;
|
|
}
|
|
|
|
bool remove(const std::string &id) override {
|
|
if (fail()) return false;
|
|
const auto found = find(id);
|
|
if (found == items.end()) return false;
|
|
items.erase(found);
|
|
calls.push_back("remove:" + id);
|
|
return true;
|
|
}
|
|
bool insert(const Node &node, const std::string &before) override {
|
|
if (fail() || find(node.attrs.id) != items.end()) return false;
|
|
auto position = before.empty() ? items.end() : find(before);
|
|
if (!before.empty() && position == items.end()) return false;
|
|
items.insert(position, node);
|
|
calls.push_back("insert:" + node.attrs.id);
|
|
return true;
|
|
}
|
|
bool update(const Node &, const Node &node) override {
|
|
if (fail()) return false;
|
|
const auto found = find(node.attrs.id);
|
|
if (found == items.end() || found->kind != node.kind) return false;
|
|
*found = node;
|
|
calls.push_back("update:" + node.attrs.id);
|
|
return true;
|
|
}
|
|
private:
|
|
bool fail() { return attempts++ == fail_at; }
|
|
std::vector<Node>::iterator find(const std::string &id) {
|
|
return std::find_if(items.begin(), items.end(),
|
|
[&](const Node &node) { return node.attrs.id == id; });
|
|
}
|
|
};
|
|
|
|
static Node model(std::initializer_list<const char *> ids) {
|
|
ps5ui::Page page("root", "Inventory");
|
|
for (const auto *id : ids) page.link(id, id, std::string(id) + ".xml");
|
|
return page.root();
|
|
}
|
|
|
|
static int test_status_changes_reuse_elements() {
|
|
toolbox_i18n::set_lang(toolbox_i18n::Lang::En);
|
|
std::vector<PluginInventoryItem> entries = {
|
|
{.plugin_id = "FTPS00001", .name = "FTP", .running = false, .auto_start = false}};
|
|
ps5ui::Page first("root", "Plugins");
|
|
onion::shellui::plugin_pages::append_plugin_list_links(first, entries);
|
|
Node current = first.root();
|
|
FakePage view(current);
|
|
entries[0].running = true;
|
|
entries[0].auto_start = true;
|
|
ps5ui::Page second("root", "Plugins");
|
|
onion::shellui::plugin_pages::append_plugin_list_links(second, entries);
|
|
TEST_ASSERT_TRUE(reconcile(view, current, second.root()));
|
|
TEST_ASSERT_EQ_INT(1, view.calls.size());
|
|
TEST_ASSERT_TRUE(view.calls[0].starts_with("update:"));
|
|
TEST_ASSERT_TRUE(view.items == second.root().children);
|
|
TEST_ASSERT_TRUE(current == second.root());
|
|
return 0;
|
|
}
|
|
|
|
static int test_structure_and_order() {
|
|
Node current = model({"a", "b", "c"});
|
|
FakePage view(current);
|
|
Node next = model({"c", "d", "a"});
|
|
TEST_ASSERT_TRUE(reconcile(view, current, next));
|
|
TEST_ASSERT_TRUE(view.items == next.children);
|
|
TEST_ASSERT_TRUE(current == next);
|
|
TEST_ASSERT_TRUE(std::find(view.calls.begin(), view.calls.end(), "remove:a") == view.calls.end());
|
|
const auto count = view.calls.size();
|
|
TEST_ASSERT_TRUE(reconcile(view, current, next));
|
|
TEST_ASSERT_EQ_INT(count, view.calls.size());
|
|
next = model({});
|
|
TEST_ASSERT_TRUE(reconcile(view, current, next));
|
|
TEST_ASSERT_TRUE(view.items.empty());
|
|
next = model({"new"});
|
|
TEST_ASSERT_TRUE(reconcile(view, current, next));
|
|
TEST_ASSERT_TRUE(view.items == next.children);
|
|
return 0;
|
|
}
|
|
|
|
static int test_failure_then_retry() {
|
|
for (int fail_at = 0; fail_at < 4; ++fail_at) {
|
|
Node current = model({"a", "b", "c"});
|
|
FakePage view(current);
|
|
view.fail_at = fail_at;
|
|
const Node next = model({"c", "d", "a"});
|
|
TEST_ASSERT_TRUE(!reconcile(view, current, next));
|
|
TEST_ASSERT_TRUE(view.items == current.children);
|
|
view.fail_at = -1;
|
|
TEST_ASSERT_TRUE(reconcile(view, current, next));
|
|
TEST_ASSERT_TRUE(view.items == next.children);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int test_all_orders_and_types() {
|
|
Node base = model({"a", "b", "c", "d"});
|
|
std::vector<Node> desired = base.children;
|
|
do {
|
|
Node current = base;
|
|
FakePage view(current);
|
|
Node next = base;
|
|
next.children = desired;
|
|
TEST_ASSERT_TRUE(reconcile(view, current, next));
|
|
TEST_ASSERT_TRUE(view.items == desired);
|
|
} while (std::next_permutation(desired.begin(), desired.end(),
|
|
[](const Node &a, const Node &b) { return a.attrs.id < b.attrs.id; }));
|
|
FakePage view(base);
|
|
Node next = base;
|
|
next.children[0].kind = Node::Kind::Label;
|
|
TEST_ASSERT_TRUE(reconcile(view, base, next));
|
|
TEST_ASSERT_TRUE(view.items == next.children);
|
|
return 0;
|
|
}
|
|
|
|
static int test_live_toggle_overrides_interaction() {
|
|
ps5ui::Page page("root", "Settings");
|
|
page.toggle("enabled", "Enabled", false);
|
|
Node current = page.root();
|
|
FakePage view(current);
|
|
// A user changed the native widget, but the authoritative source is still false.
|
|
view.items[0].attrs.value = "1";
|
|
TEST_ASSERT_TRUE(reconcile(view, current, page.root()));
|
|
TEST_ASSERT_TRUE(view.items == page.root().children);
|
|
TEST_ASSERT_EQ_INT(1, view.calls.size());
|
|
return 0;
|
|
}
|
|
|
|
static int test_invalid_models_do_not_mutate() {
|
|
Node current = model({"a"});
|
|
FakePage view(current);
|
|
Node next = model({"a", "a"});
|
|
TEST_ASSERT_TRUE(!reconcile(view, current, next));
|
|
next = model({"b"});
|
|
next.children[0].kind = Node::Kind::SettingList;
|
|
TEST_ASSERT_TRUE(!reconcile(view, current, next));
|
|
next = model({"a"});
|
|
next.attrs.id = "different-page";
|
|
TEST_ASSERT_TRUE(!reconcile(view, current, next));
|
|
TEST_ASSERT_TRUE(view.calls.empty());
|
|
return 0;
|
|
}
|
|
|
|
static int test_title_and_removed_description() {
|
|
Node current = model({"a"});
|
|
current.children[0].attrs.second_title = "Old status";
|
|
FakePage view(current);
|
|
Node next = model({"a"});
|
|
next.attrs.title = "Updated name";
|
|
TEST_ASSERT_TRUE(reconcile(view, current, next));
|
|
TEST_ASSERT_STREQ("Updated name", view.title.c_str());
|
|
TEST_ASSERT_TRUE(view.items == next.children);
|
|
TEST_ASSERT_TRUE(current == next);
|
|
return 0;
|
|
}
|
|
|
|
static int test_nested_return_context() {
|
|
using toolbox::Page;
|
|
ToolboxUiState state;
|
|
state.set_active_page(Page::Plugins);
|
|
state.set_active_page(Page::PluginConfig);
|
|
state.active_plugin = "FTPS00001";
|
|
state.set_active_page(Page::DynamicPlugin);
|
|
state.set_active_page(Page::DynamicPlugin);
|
|
state.active_plugin = "temporary-child-context";
|
|
state.leave_page(Page::DynamicPlugin);
|
|
TEST_ASSERT_TRUE(state.active_page == Page::PluginConfig);
|
|
TEST_ASSERT_TRUE(state.parent_page == Page::Plugins);
|
|
TEST_ASSERT_STREQ("FTPS00001", state.active_plugin.c_str());
|
|
state.leave_page(Page::PluginConfig);
|
|
TEST_ASSERT_TRUE(state.active_page == Page::Plugins);
|
|
TEST_ASSERT_TRUE(state.parent_pages.empty());
|
|
state.set_active_page(Page::PluginConfig);
|
|
state.set_active_page(Page::DebugSettings);
|
|
TEST_ASSERT_TRUE(state.parent_pages.empty());
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int test_settings_page_update_suite() {
|
|
int fails = 0;
|
|
fails += onion_test_run("settings.status_reuses_elements", test_status_changes_reuse_elements);
|
|
fails += onion_test_run("settings.structure_order", test_structure_and_order);
|
|
fails += onion_test_run("settings.retry", test_failure_then_retry);
|
|
fails += onion_test_run("settings.permutations", test_all_orders_and_types);
|
|
fails += onion_test_run("settings.interactive_value", test_live_toggle_overrides_interaction);
|
|
fails += onion_test_run("settings.invalid_model", test_invalid_models_do_not_mutate);
|
|
fails += onion_test_run("settings.title_description", test_title_and_removed_description);
|
|
fails += onion_test_run("settings.nested_return", test_nested_return_context);
|
|
return fails;
|
|
}
|