List recently opened projects under Open Recent
Projects opened or saved go into GTK's shared recent list, signed with the effector application name, and the project menu lists the newest ten of them. The GUI tests point XDG_DATA_HOME at a temporary folder so they leave the user's own list alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+76
-1
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -13,6 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "cli/options.hpp"
|
||||
#include "core/project.hpp"
|
||||
#include "core/registry.hpp"
|
||||
#include "gui/window.hpp"
|
||||
|
||||
@@ -332,6 +334,72 @@ void runTests(Gtk::Window& window) {
|
||||
pump();
|
||||
}
|
||||
|
||||
// The labels of the entries under the "Open Recent" submenu of `model`, or
|
||||
// nothing when there is no such submenu.
|
||||
void recentLabels(GMenuModel* model, std::vector<std::string>& out, bool& found) {
|
||||
for (int i = 0; i < g_menu_model_get_n_items(model); ++i) {
|
||||
char* label = nullptr;
|
||||
g_menu_model_get_item_attribute(model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
|
||||
const std::string text = label != nullptr ? label : "";
|
||||
g_free(label);
|
||||
if (GMenuModel* section = g_menu_model_get_item_link(model, i, G_MENU_LINK_SECTION)) {
|
||||
recentLabels(section, out, found);
|
||||
g_object_unref(section);
|
||||
}
|
||||
if (GMenuModel* submenu = g_menu_model_get_item_link(model, i, G_MENU_LINK_SUBMENU)) {
|
||||
if (text == "Open Recent") {
|
||||
found = true;
|
||||
for (int j = 0; j < g_menu_model_get_n_items(submenu); ++j) {
|
||||
char* entry = nullptr;
|
||||
g_menu_model_get_item_attribute(submenu, j, G_MENU_ATTRIBUTE_LABEL, "s", &entry);
|
||||
out.emplace_back(entry != nullptr ? entry : "");
|
||||
g_free(entry);
|
||||
}
|
||||
}
|
||||
g_object_unref(submenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> recentEntries(Gtk::Window& window) {
|
||||
std::vector<Gtk::MenuButton*> buttons;
|
||||
collect<Gtk::MenuButton>(window, [](Gtk::MenuButton& b) { return b.get_tooltip_text() == "Project menu"; },
|
||||
buttons);
|
||||
std::vector<std::string> labels;
|
||||
bool found = false;
|
||||
if (buttons.empty()) return labels;
|
||||
auto* popover = dynamic_cast<Gtk::PopoverMenu*>(buttons.front()->get_popover());
|
||||
if (popover == nullptr || !popover->get_menu_model()) return labels;
|
||||
recentLabels(popover->get_menu_model()->gobj(), labels, found);
|
||||
if (!found) labels.emplace_back("<no Open Recent submenu>");
|
||||
return labels;
|
||||
}
|
||||
|
||||
void testRecentProjects(Gtk::Window& window, const std::filesystem::path& dir) {
|
||||
std::cout << "recent projects\n";
|
||||
check(recentEntries(window) == std::vector<std::string>{"No Recent Projects"},
|
||||
"Open Recent starts out empty");
|
||||
|
||||
// Two projects of the same name in different folders, and a third.
|
||||
std::filesystem::create_directories(dir / "first");
|
||||
std::filesystem::create_directories(dir / "second");
|
||||
const std::filesystem::path paths[] = {dir / "first" / "my_look.json", dir / "second" / "my_look.json",
|
||||
dir / "other.json"};
|
||||
effector::Project project;
|
||||
project.stages.push_back({effector::effectByName("blur"), {}}); // renders with no sources
|
||||
for (const auto& path : paths) {
|
||||
effector::saveProject(path.string(), project);
|
||||
window.activate_action("win.open-recent",
|
||||
Glib::Variant<Glib::ustring>::create(Glib::filename_to_uri(path.string())));
|
||||
pump(50);
|
||||
}
|
||||
pump(500); // the recent list is written, and announced, from an idle
|
||||
check(window.get_title() == "other — Effector", "an entry opens its project");
|
||||
const auto labels = recentEntries(window);
|
||||
check(labels == std::vector<std::string>{"other", "my__look (second)", "my__look (first)"},
|
||||
"opened projects are listed newest first, a shared name with its folder");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -344,17 +412,24 @@ int main(int argc, char** argv) {
|
||||
return 77;
|
||||
}
|
||||
|
||||
// Keep the projects the tests open out of the user's own recent list.
|
||||
std::string dataTemplate = (std::filesystem::temp_directory_path() / "effector-gui-XXXXXX").string();
|
||||
const std::filesystem::path dataDir = g_mkdtemp(dataTemplate.data());
|
||||
g_setenv("XDG_DATA_HOME", dataDir.c_str(), TRUE);
|
||||
|
||||
auto app = Gtk::Application::create("org.effector.GuiTests",
|
||||
Gio::Application::Flags::NON_UNIQUE);
|
||||
app->signal_activate().connect([&app] {
|
||||
app->signal_activate().connect([&app, &dataDir] {
|
||||
auto window = std::make_unique<effector::EffectorWindow>(effector::Options{});
|
||||
app->add_window(*window);
|
||||
window->present();
|
||||
pump(300);
|
||||
runTests(*window);
|
||||
testRecentProjects(*window, dataDir / "projects");
|
||||
app->remove_window(*window);
|
||||
});
|
||||
const int status = app->run(argc > 0 ? 1 : 0, argv);
|
||||
std::filesystem::remove_all(dataDir);
|
||||
if (status != 0) return status;
|
||||
std::cout << (failures == 0 ? "all passed\n" : std::to_string(failures) + " failed\n");
|
||||
return failures;
|
||||
|
||||
Reference in New Issue
Block a user