mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-10-25 10:02:41 -05:00
Support nested macro groups in data model
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
#include "macro-action-macro.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static std::atomic_bool abortMacroWait = {false};
|
||||
@@ -124,25 +126,58 @@ bool IsGroupMacro(Macro *macro)
|
||||
|
||||
std::vector<std::shared_ptr<Macro>> GetGroupMacroEntries(Macro *macro)
|
||||
{
|
||||
if (!macro || !macro->IsGroup()) {
|
||||
auto entries = GetGroupDescendants(macro);
|
||||
entries.erase(std::remove_if(entries.begin(), entries.end(),
|
||||
[](const std::shared_ptr<Macro> &entry) {
|
||||
return entry->IsGroup();
|
||||
}),
|
||||
entries.end());
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Macro>> GetGroupChildren(const Macro *group)
|
||||
{
|
||||
if (!group || !group->IsGroup()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Macro>> entries;
|
||||
entries.reserve(macro->GroupSize());
|
||||
std::vector<std::shared_ptr<Macro>> children;
|
||||
for (const auto ¯o : GetTopLevelMacros()) {
|
||||
if (macro->Parent().get() == group) {
|
||||
children.emplace_back(macro);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
const auto ¯os = GetTopLevelMacros();
|
||||
for (auto it = macros.begin(); it < macros.end(); it++) {
|
||||
if ((*it)->Name() != macro->Name()) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t i = 1; i <= macro->GroupSize(); i++) {
|
||||
entries.emplace_back(*std::next(it, i));
|
||||
}
|
||||
break;
|
||||
std::vector<std::shared_ptr<Macro>> GetGroupDescendants(const Macro *group)
|
||||
{
|
||||
if (!group || !group->IsGroup()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return entries;
|
||||
std::vector<std::shared_ptr<Macro>> descendants;
|
||||
descendants.reserve(group->GroupSize());
|
||||
for (const auto ¯o : GetTopLevelMacros()) {
|
||||
if (IsMacroInGroup(macro.get(), group)) {
|
||||
descendants.emplace_back(macro);
|
||||
}
|
||||
}
|
||||
return descendants;
|
||||
}
|
||||
|
||||
bool IsMacroInGroup(const Macro *macro, const Macro *group)
|
||||
{
|
||||
if (!macro || !group) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto parent = macro->Parent(); parent; parent = parent->Parent()) {
|
||||
if (parent.get() == group) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::condition_variable &GetMacroWaitCV()
|
||||
|
||||
@@ -37,7 +37,11 @@ EXPORT std::optional<std::deque<std::shared_ptr<MacroCondition>>>
|
||||
GetMacroConditions(Macro *);
|
||||
|
||||
EXPORT bool IsGroupMacro(Macro *);
|
||||
// Returns all non-group macros within the group including nested groups
|
||||
EXPORT std::vector<std::shared_ptr<Macro>> GetGroupMacroEntries(Macro *);
|
||||
std::vector<std::shared_ptr<Macro>> GetGroupChildren(const Macro *group);
|
||||
std::vector<std::shared_ptr<Macro>> GetGroupDescendants(const Macro *group);
|
||||
bool IsMacroInGroup(const Macro *macro, const Macro *group);
|
||||
|
||||
EXPORT std::condition_variable &GetMacroWaitCV();
|
||||
EXPORT std::condition_variable &GetMacroTransitionCV();
|
||||
|
||||
@@ -155,7 +155,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
|
||||
static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> ¯os,
|
||||
const std::shared_ptr<Macro> &group)
|
||||
{
|
||||
auto subitems = GetGroupMacroEntries(group.get());
|
||||
auto subitems = GetGroupDescendants(group.get());
|
||||
|
||||
// Remove subitems which were already selected to avoid duplicates
|
||||
for (const auto &subitem : subitems) {
|
||||
|
||||
@@ -222,7 +222,7 @@ static void deleteGroupWithChildrenFromData(const char *jsonData)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto children = GetGroupMacroEntries(group.get());
|
||||
const auto children = GetGroupChildren(group.get());
|
||||
|
||||
auto *const window = AdvSceneSwitcher::window;
|
||||
if (SettingsWindowIsOpened() && window) {
|
||||
@@ -1113,7 +1113,7 @@ void RegisterGroupCreateUndoRedo(const std::string &groupName)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto children = GetGroupMacroEntries(group.get());
|
||||
const auto children = GetGroupChildren(group.get());
|
||||
OBSDataArrayAutoRelease childArray = buildChildArray(children);
|
||||
|
||||
OBSDataAutoRelease undoData = obs_data_create();
|
||||
@@ -1157,7 +1157,7 @@ void RegisterGroupRemoveUndoRedo(const std::string &groupName)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto children = GetGroupMacroEntries(group.get());
|
||||
const auto children = GetGroupChildren(group.get());
|
||||
OBSDataArrayAutoRelease childArray = buildChildArray(children);
|
||||
|
||||
OBSDataAutoRelease undoData = obs_data_create();
|
||||
@@ -1205,7 +1205,7 @@ void RegisterGroupDeleteUndoRedo(Macro *macro)
|
||||
OBSDataAutoRelease groupData = obs_data_create();
|
||||
macro->Save(groupData);
|
||||
|
||||
const auto children = GetGroupMacroEntries(macro);
|
||||
const auto children = GetGroupChildren(macro);
|
||||
OBSDataArrayAutoRelease childArray = obs_data_array_create();
|
||||
for (const auto &child : children) {
|
||||
OBSDataAutoRelease childData = obs_data_create();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#undef max
|
||||
@@ -62,11 +63,23 @@ Macro::CreateGroup(const std::string &name,
|
||||
{
|
||||
auto group = std::make_shared<Macro>(name);
|
||||
group->_registerHotkeys = false;
|
||||
for (auto &c : children) {
|
||||
c->SetParent(group);
|
||||
}
|
||||
group->_isGroup = true;
|
||||
group->_groupSize = children.size();
|
||||
|
||||
if (!children.empty()) {
|
||||
const auto parent = children.front()->Parent();
|
||||
const bool haveSameParent = std::all_of(
|
||||
children.begin(), children.end(),
|
||||
[&parent](const std::shared_ptr<Macro> &child) {
|
||||
return child->Parent() == parent;
|
||||
});
|
||||
if (haveSameParent) {
|
||||
PrepareMoveToGroup(parent, group);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &child : children) {
|
||||
PrepareMoveToGroup(group, child);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -78,11 +91,11 @@ void Macro::RemoveGroup(std::shared_ptr<Macro> group)
|
||||
return;
|
||||
}
|
||||
|
||||
auto size = group->GroupSize();
|
||||
for (uint32_t i = 1; i <= size; i++) {
|
||||
auto m = std::next(it, i);
|
||||
(*m)->SetParent(nullptr);
|
||||
const auto parent = group->Parent();
|
||||
for (const auto &child : GetGroupChildren(group.get())) {
|
||||
child->SetParent(parent);
|
||||
}
|
||||
AddToGroupSizes(parent, -1);
|
||||
|
||||
macros.erase(it);
|
||||
}
|
||||
@@ -106,15 +119,72 @@ void Macro::PrepareMoveToGroup(std::shared_ptr<Macro> group,
|
||||
return;
|
||||
}
|
||||
|
||||
// Potentially remove from old group
|
||||
auto oldGroup = item->Parent();
|
||||
if (oldGroup) {
|
||||
oldGroup->_groupSize--;
|
||||
if (group &&
|
||||
(group == item || IsMacroInGroup(group.get(), item.get()))) {
|
||||
blog(LOG_WARNING, "cannot move \"%s\" into itself",
|
||||
item->Name().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
const int count = 1 + (int)item->_groupSize;
|
||||
AddToGroupSizes(item->Parent(), -count);
|
||||
item->SetParent(group);
|
||||
if (group) {
|
||||
group->_groupSize++;
|
||||
AddToGroupSizes(group, count);
|
||||
}
|
||||
|
||||
void Macro::AddToGroupSizes(std::shared_ptr<Macro> group, int count)
|
||||
{
|
||||
for (; group; group = group->Parent()) {
|
||||
assert((int)group->_groupSize + count >= 0);
|
||||
group->_groupSize += count;
|
||||
}
|
||||
}
|
||||
|
||||
void Macro::RestoreGroupHierarchy(
|
||||
const std::deque<std::shared_ptr<Macro>> ¯os)
|
||||
{
|
||||
struct OpenGroup {
|
||||
std::shared_ptr<Macro> group;
|
||||
uint32_t remaining;
|
||||
};
|
||||
std::vector<OpenGroup> openGroups;
|
||||
|
||||
for (size_t i = 0; i < macros.size(); i++) {
|
||||
const auto ¯o = macros[i];
|
||||
while (!openGroups.empty() &&
|
||||
openGroups.back().remaining == 0) {
|
||||
openGroups.pop_back();
|
||||
}
|
||||
|
||||
macro->SetParent(openGroups.empty() ? nullptr
|
||||
: openGroups.back().group);
|
||||
for (auto &openGroup : openGroups) {
|
||||
openGroup.remaining--;
|
||||
}
|
||||
|
||||
if (!macro->IsGroup()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto maxSize = openGroups.empty()
|
||||
? (uint32_t)(macros.size() - i - 1)
|
||||
: openGroups.back().remaining;
|
||||
auto size = macro->_groupSize;
|
||||
if (size > maxSize) {
|
||||
blog(LOG_WARNING,
|
||||
"invalid group size detected for \"%s\" - "
|
||||
"reducing size from %u to %u",
|
||||
macro->Name().c_str(), size, maxSize);
|
||||
size = maxSize;
|
||||
}
|
||||
openGroups.push_back({macro, size});
|
||||
}
|
||||
|
||||
for (const auto ¯o : macros) {
|
||||
macro->_groupSize = 0;
|
||||
}
|
||||
for (const auto ¯o : macros) {
|
||||
AddToGroupSizes(macro->Parent(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,8 +409,7 @@ bool Macro::PerformActions(bool match, bool forceParallel, bool ignorePause)
|
||||
}
|
||||
|
||||
_lastExecutionTime = std::chrono::high_resolution_clock::now();
|
||||
auto group = _parent.lock();
|
||||
if (group) {
|
||||
for (auto group = _parent.lock(); group; group = group->Parent()) {
|
||||
group->_lastExecutionTime = _lastExecutionTime;
|
||||
}
|
||||
if (_runCount != std::numeric_limits<int>::max()) {
|
||||
@@ -1250,42 +1319,10 @@ void LoadMacros(obs_data_t *obj)
|
||||
}
|
||||
obs_data_array_release(macroArray);
|
||||
|
||||
int groupCount = 0;
|
||||
std::shared_ptr<Macro> group;
|
||||
std::vector<std::shared_ptr<Macro>> invalidGroups;
|
||||
Macro::RestoreGroupHierarchy(macros);
|
||||
for (const auto &m : macros) {
|
||||
if (groupCount && m->IsGroup()) {
|
||||
blog(LOG_ERROR,
|
||||
"nested group detected - will delete \"%s\"",
|
||||
m->Name().c_str());
|
||||
invalidGroups.emplace_back(m);
|
||||
continue;
|
||||
}
|
||||
if (groupCount) {
|
||||
m->SetParent(group);
|
||||
groupCount--;
|
||||
}
|
||||
if (m->IsGroup()) {
|
||||
groupCount = m->GroupSize();
|
||||
group = m;
|
||||
}
|
||||
m->PostLoad();
|
||||
}
|
||||
|
||||
if (groupCount) {
|
||||
blog(LOG_ERROR,
|
||||
"invalid group size detected - will delete \"%s\"",
|
||||
group->Name().c_str());
|
||||
invalidGroups.emplace_back(group);
|
||||
}
|
||||
|
||||
for (auto &m : invalidGroups) {
|
||||
auto it = std::find(macros.begin(), macros.end(), m);
|
||||
if (it == macros.end()) {
|
||||
continue;
|
||||
}
|
||||
macros.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckMacros()
|
||||
|
||||
@@ -121,7 +121,10 @@ public:
|
||||
std::shared_ptr<Macro> item);
|
||||
static void PrepareMoveToGroup(std::shared_ptr<Macro> group,
|
||||
std::shared_ptr<Macro> item);
|
||||
static void
|
||||
RestoreGroupHierarchy(const std::deque<std::shared_ptr<Macro>> &);
|
||||
bool IsGroup() const { return _isGroup; }
|
||||
// Number of all macros and groups nested within this group
|
||||
uint32_t GroupSize() const { return _groupSize; }
|
||||
void ResetGroupSize() { _groupSize = 0; }
|
||||
bool IsSubitem() const { return !_parent.expired(); }
|
||||
@@ -191,6 +194,8 @@ private:
|
||||
std::deque<std::shared_ptr<MacroAction>> _actions;
|
||||
std::deque<std::shared_ptr<MacroAction>> _elseActions;
|
||||
|
||||
static void AddToGroupSizes(std::shared_ptr<Macro> group, int count);
|
||||
|
||||
std::weak_ptr<Macro> _parent;
|
||||
Macro *_nestedParentMacro = nullptr;
|
||||
bool _isNested = false;
|
||||
|
||||
@@ -137,6 +137,9 @@ target_sources(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE test-macro.cpp
|
||||
test-macro-condition.cpp
|
||||
test-macro-group.cpp
|
||||
macro-test-helpers.cpp
|
||||
macro-test-helpers.hpp
|
||||
stubs/path-helpers.cpp
|
||||
stubs/ui-helpers.cpp
|
||||
stubs/plugin-state-helpers.cpp
|
||||
|
||||
122
tests/macro-test-helpers.cpp
Normal file
122
tests/macro-test-helpers.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "macro-test-helpers.hpp"
|
||||
|
||||
#include <macro.hpp>
|
||||
#include <macro-helpers.hpp>
|
||||
|
||||
#include <cctype>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
namespace advss::test {
|
||||
|
||||
namespace {
|
||||
|
||||
class LayoutParser {
|
||||
public:
|
||||
explicit LayoutParser(const std::string &layout) : _text(layout) {}
|
||||
|
||||
std::deque<std::shared_ptr<Macro>> Parse()
|
||||
{
|
||||
std::deque<std::shared_ptr<Macro>> result;
|
||||
ParseList(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Macro>>
|
||||
ParseList(std::deque<std::shared_ptr<Macro>> &out)
|
||||
{
|
||||
std::vector<std::shared_ptr<Macro>> items;
|
||||
SkipSpaces();
|
||||
while (_pos < _text.size() && _text[_pos] != ']' &&
|
||||
_text[_pos] != '}') {
|
||||
items.emplace_back(ParseItem(out));
|
||||
SkipSpaces();
|
||||
if (_pos < _text.size() && _text[_pos] == ',') {
|
||||
_pos++;
|
||||
}
|
||||
SkipSpaces();
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
std::shared_ptr<Macro>
|
||||
ParseItem(std::deque<std::shared_ptr<Macro>> &out)
|
||||
{
|
||||
std::string name;
|
||||
while (_pos < _text.size() &&
|
||||
std::isalnum(static_cast<unsigned char>(_text[_pos]))) {
|
||||
name += _text[_pos++];
|
||||
}
|
||||
|
||||
if (_pos >= _text.size() ||
|
||||
(_text[_pos] != '[' && _text[_pos] != '{')) {
|
||||
auto macro = std::make_shared<Macro>(name);
|
||||
out.emplace_back(macro);
|
||||
return macro;
|
||||
}
|
||||
|
||||
const bool collapsed = _text[_pos] == '{';
|
||||
_pos++;
|
||||
std::deque<std::shared_ptr<Macro>> descendants;
|
||||
auto children = ParseList(descendants);
|
||||
_pos++;
|
||||
|
||||
auto group = Macro::CreateGroup(name, children);
|
||||
group->SetCollapsed(collapsed);
|
||||
out.emplace_back(group);
|
||||
out.insert(out.end(), descendants.begin(), descendants.end());
|
||||
return group;
|
||||
}
|
||||
|
||||
void SkipSpaces()
|
||||
{
|
||||
while (_pos < _text.size() && _text[_pos] == ' ') {
|
||||
_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
std::string _text;
|
||||
size_t _pos = 0;
|
||||
};
|
||||
|
||||
std::string describeChildren(const Macro *parent)
|
||||
{
|
||||
std::string result;
|
||||
for (const auto ¯o : GetTopLevelMacros()) {
|
||||
if (macro->Parent().get() != parent) {
|
||||
continue;
|
||||
}
|
||||
if (!result.empty()) {
|
||||
result += ", ";
|
||||
}
|
||||
result += macro->Name();
|
||||
if (!macro->IsGroup()) {
|
||||
continue;
|
||||
}
|
||||
const bool collapsed = macro->IsCollapsed();
|
||||
result += collapsed ? "{" : "[";
|
||||
result += describeChildren(macro.get());
|
||||
result += collapsed ? "}" : "]";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetupMacros(const std::string &layout)
|
||||
{
|
||||
GetTopLevelMacros() = LayoutParser(layout).Parse();
|
||||
}
|
||||
|
||||
std::string DescribeMacros()
|
||||
{
|
||||
return describeChildren(nullptr);
|
||||
}
|
||||
|
||||
std::shared_ptr<Macro> GetMacro(const std::string &name)
|
||||
{
|
||||
return GetWeakMacroByName(name.c_str()).lock();
|
||||
}
|
||||
|
||||
} // namespace advss::test
|
||||
21
tests/macro-test-helpers.hpp
Normal file
21
tests/macro-test-helpers.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace advss {
|
||||
class Macro;
|
||||
}
|
||||
|
||||
namespace advss::test {
|
||||
|
||||
// Replaces all macros with the given layout.
|
||||
// Layout syntax: "A, G1[B, C], G2{D}" where [] is an expanded and {} a
|
||||
// collapsed group.
|
||||
void SetupMacros(const std::string &layout);
|
||||
|
||||
// Describes the current macros using the same syntax as SetupMacros()
|
||||
std::string DescribeMacros();
|
||||
|
||||
std::shared_ptr<Macro> GetMacro(const std::string &name);
|
||||
|
||||
} // namespace advss::test
|
||||
263
tests/test-macro-group.cpp
Normal file
263
tests/test-macro-group.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
#include "catch.hpp"
|
||||
#include "macro-test-helpers.hpp"
|
||||
|
||||
#include <macro.hpp>
|
||||
#include <macro-helpers.hpp>
|
||||
|
||||
#include <obs.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace advss;
|
||||
using namespace advss::test;
|
||||
|
||||
using Names = std::vector<std::string>;
|
||||
|
||||
Names names(const std::vector<std::shared_ptr<Macro>> ¯os)
|
||||
{
|
||||
Names result;
|
||||
for (const auto ¯o : macros) {
|
||||
result.emplace_back(macro->Name());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t groupSize(const std::string &name)
|
||||
{
|
||||
auto group = GetMacro(name);
|
||||
REQUIRE(group);
|
||||
REQUIRE(group->IsGroup());
|
||||
return group->GroupSize();
|
||||
}
|
||||
|
||||
void loadFromJson(const char *json)
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create_from_json(json);
|
||||
REQUIRE(data);
|
||||
LoadMacros(data);
|
||||
}
|
||||
|
||||
void saveAndLoad()
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
SaveMacros(data);
|
||||
GetTopLevelMacros().clear();
|
||||
LoadMacros(data);
|
||||
}
|
||||
|
||||
struct ClearMacros {
|
||||
~ClearMacros() { GetTopLevelMacros().clear(); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Nested group sizes", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1[B, G2[C, D], E], F");
|
||||
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, G2[C, D], E], F");
|
||||
REQUIRE(groupSize("G1") == 5);
|
||||
REQUIRE(groupSize("G2") == 2);
|
||||
REQUIRE(GetMacro("C")->Parent() == GetMacro("G2"));
|
||||
REQUIRE(GetMacro("G2")->Parent() == GetMacro("G1"));
|
||||
REQUIRE_FALSE(GetMacro("A")->Parent());
|
||||
}
|
||||
|
||||
TEST_CASE("Group helpers", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1[B, G2[C, D], E, G3[]], F");
|
||||
auto g1 = GetMacro("G1").get();
|
||||
auto g2 = GetMacro("G2").get();
|
||||
|
||||
REQUIRE(names(GetGroupChildren(g1)) == Names{"B", "G2", "E", "G3"});
|
||||
REQUIRE(names(GetGroupDescendants(g1)) ==
|
||||
Names{"B", "G2", "C", "D", "E", "G3"});
|
||||
REQUIRE(names(GetGroupMacroEntries(g1)) == Names{"B", "C", "D", "E"});
|
||||
REQUIRE(names(GetGroupMacroEntries(g2)) == Names{"C", "D"});
|
||||
REQUIRE(GetGroupMacroEntries(GetMacro("G3").get()).empty());
|
||||
REQUIRE(GetGroupChildren(GetMacro("A").get()).empty());
|
||||
|
||||
REQUIRE(IsMacroInGroup(GetMacro("C").get(), g1));
|
||||
REQUIRE(IsMacroInGroup(GetMacro("C").get(), g2));
|
||||
REQUIRE_FALSE(IsMacroInGroup(GetMacro("B").get(), g2));
|
||||
REQUIRE_FALSE(IsMacroInGroup(g1, g1));
|
||||
REQUIRE_FALSE(IsMacroInGroup(GetMacro("A").get(), g1));
|
||||
}
|
||||
|
||||
TEST_CASE("Move macros between nested groups", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1[B, G2[C, D], E], F");
|
||||
|
||||
SECTION("Group with children to top level")
|
||||
{
|
||||
Macro::PrepareMoveToGroup(nullptr, GetMacro("G2"));
|
||||
REQUIRE(groupSize("G1") == 2);
|
||||
REQUIRE(groupSize("G2") == 2);
|
||||
REQUIRE_FALSE(GetMacro("G2")->Parent());
|
||||
}
|
||||
|
||||
SECTION("Macro into nested group")
|
||||
{
|
||||
Macro::PrepareMoveToGroup(GetMacro("G2"), GetMacro("A"));
|
||||
REQUIRE(groupSize("G1") == 6);
|
||||
REQUIRE(groupSize("G2") == 3);
|
||||
REQUIRE(GetMacro("A")->Parent() == GetMacro("G2"));
|
||||
}
|
||||
|
||||
SECTION("Macro from nested group to outer group")
|
||||
{
|
||||
Macro::PrepareMoveToGroup(GetMacro("G1"), GetMacro("C"));
|
||||
REQUIRE(groupSize("G1") == 5);
|
||||
REQUIRE(groupSize("G2") == 1);
|
||||
}
|
||||
|
||||
SECTION("Group into itself is rejected")
|
||||
{
|
||||
Macro::PrepareMoveToGroup(GetMacro("G1"), GetMacro("G1"));
|
||||
REQUIRE_FALSE(GetMacro("G1")->Parent());
|
||||
REQUIRE(groupSize("G1") == 5);
|
||||
}
|
||||
|
||||
SECTION("Group into own descendant is rejected")
|
||||
{
|
||||
Macro::PrepareMoveToGroup(GetMacro("G2"), GetMacro("G1"));
|
||||
REQUIRE_FALSE(GetMacro("G1")->Parent());
|
||||
REQUIRE(groupSize("G1") == 5);
|
||||
REQUIRE(groupSize("G2") == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Create group from nested macros", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1[B, C, D]");
|
||||
std::vector<std::shared_ptr<Macro>> children = {GetMacro("B"),
|
||||
GetMacro("C")};
|
||||
auto group = Macro::CreateGroup("G2", children);
|
||||
|
||||
REQUIRE(group->Parent() == GetMacro("G1"));
|
||||
REQUIRE(group->GroupSize() == 2);
|
||||
REQUIRE(groupSize("G1") == 4);
|
||||
REQUIRE(GetMacro("B")->Parent() == group);
|
||||
}
|
||||
|
||||
TEST_CASE("Remove nested group", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1[B, G2[C, D], E], F");
|
||||
|
||||
SECTION("Inner group")
|
||||
{
|
||||
Macro::RemoveGroup(GetMacro("G2"));
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, C, D, E], F");
|
||||
REQUIRE(groupSize("G1") == 4);
|
||||
}
|
||||
|
||||
SECTION("Outer group")
|
||||
{
|
||||
Macro::RemoveGroup(GetMacro("G1"));
|
||||
REQUIRE(DescribeMacros() == "A, B, G2[C, D], E, F");
|
||||
REQUIRE(groupSize("G2") == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Save and load nested groups", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("A, G1{B, G2[C, G3{D}], E, G4[]}, F");
|
||||
saveAndLoad();
|
||||
|
||||
REQUIRE(DescribeMacros() == "A, G1{B, G2[C, G3{D}], E, G4[]}, F");
|
||||
REQUIRE(groupSize("G1") == 7);
|
||||
REQUIRE(groupSize("G2") == 3);
|
||||
REQUIRE(groupSize("G3") == 1);
|
||||
REQUIRE(groupSize("G4") == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Saved group size is number of descendants", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("G1[A, G2[B, C]]");
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
SaveMacros(data);
|
||||
|
||||
OBSDataArrayAutoRelease array = obs_data_get_array(data, "macros");
|
||||
REQUIRE(obs_data_array_count(array) == 5);
|
||||
OBSDataAutoRelease g1 = obs_data_array_item(array, 0);
|
||||
OBSDataAutoRelease g1Data = obs_data_get_obj(g1, "groupData");
|
||||
REQUIRE(obs_data_get_int(g1Data, "size") == 4);
|
||||
OBSDataAutoRelease g2 = obs_data_array_item(array, 2);
|
||||
OBSDataAutoRelease g2Data = obs_data_get_obj(g2, "groupData");
|
||||
REQUIRE(obs_data_get_int(g2Data, "size") == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Load single level groups", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
loadFromJson(R"({"macros": [
|
||||
{"name": "A"},
|
||||
{"name": "G1", "group": true,
|
||||
"groupData": {"size": 2, "collapsed": true}},
|
||||
{"name": "B"},
|
||||
{"name": "C"},
|
||||
{"name": "D"}
|
||||
]})");
|
||||
|
||||
REQUIRE(DescribeMacros() == "A, G1{B, C}, D");
|
||||
REQUIRE(groupSize("G1") == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Load groups with invalid sizes", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
|
||||
SECTION("Group size exceeds number of macros")
|
||||
{
|
||||
loadFromJson(R"({"macros": [
|
||||
{"name": "A"},
|
||||
{"name": "G1", "group": true, "groupData": {"size": 5}},
|
||||
{"name": "B"},
|
||||
{"name": "C"}
|
||||
]})");
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, C]");
|
||||
REQUIRE(groupSize("G1") == 2);
|
||||
}
|
||||
|
||||
SECTION("Nested group exceeds parent group")
|
||||
{
|
||||
loadFromJson(R"({"macros": [
|
||||
{"name": "G1", "group": true, "groupData": {"size": 2}},
|
||||
{"name": "G2", "group": true, "groupData": {"size": 3}},
|
||||
{"name": "A"},
|
||||
{"name": "B"},
|
||||
{"name": "C"}
|
||||
]})");
|
||||
REQUIRE(DescribeMacros() == "G1[G2[A]], B, C");
|
||||
REQUIRE(groupSize("G1") == 2);
|
||||
REQUIRE(groupSize("G2") == 1);
|
||||
}
|
||||
|
||||
SECTION("Empty group at the end")
|
||||
{
|
||||
loadFromJson(R"({"macros": [
|
||||
{"name": "A"},
|
||||
{"name": "G1", "group": true, "groupData": {"size": 1}}
|
||||
]})");
|
||||
REQUIRE(DescribeMacros() == "A, G1[]");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Execution time is propagated to all parent groups", "[macro-group]")
|
||||
{
|
||||
ClearMacros clear;
|
||||
SetupMacros("G1[G2[A]], B");
|
||||
const auto start = std::chrono::high_resolution_clock::now();
|
||||
GetMacro("A")->PerformActions(true);
|
||||
|
||||
REQUIRE(GetMacro("G1")->WasExecutedSince(start));
|
||||
REQUIRE(GetMacro("G2")->WasExecutedSince(start));
|
||||
REQUIRE_FALSE(GetMacro("B")->WasExecutedSince(start));
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "catch.hpp"
|
||||
#include "macro-test-helpers.hpp"
|
||||
#include "ui-test-helpers.hpp"
|
||||
|
||||
#include <macro.hpp>
|
||||
@@ -16,7 +17,6 @@
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
@@ -28,109 +28,6 @@ using Names = std::vector<std::string>;
|
||||
|
||||
const std::string newGroupName = "AdvSceneSwitcher.macroTab.defaultGroupName";
|
||||
|
||||
// Layout syntax: "A, G1[B, C], G2{D}" where [] is an expanded and {} a
|
||||
// collapsed group
|
||||
class LayoutParser {
|
||||
public:
|
||||
explicit LayoutParser(const std::string &layout) : _text(layout) {}
|
||||
|
||||
std::deque<std::shared_ptr<Macro>> Parse()
|
||||
{
|
||||
std::deque<std::shared_ptr<Macro>> result;
|
||||
ParseList(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Macro>>
|
||||
ParseList(std::deque<std::shared_ptr<Macro>> &out)
|
||||
{
|
||||
std::vector<std::shared_ptr<Macro>> items;
|
||||
SkipSpaces();
|
||||
while (_pos < _text.size() && _text[_pos] != ']' &&
|
||||
_text[_pos] != '}') {
|
||||
items.emplace_back(ParseItem(out));
|
||||
SkipSpaces();
|
||||
if (_pos < _text.size() && _text[_pos] == ',') {
|
||||
_pos++;
|
||||
}
|
||||
SkipSpaces();
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
std::shared_ptr<Macro>
|
||||
ParseItem(std::deque<std::shared_ptr<Macro>> &out)
|
||||
{
|
||||
std::string name;
|
||||
while (_pos < _text.size() &&
|
||||
std::isalnum(static_cast<unsigned char>(_text[_pos]))) {
|
||||
name += _text[_pos++];
|
||||
}
|
||||
|
||||
if (_pos >= _text.size() ||
|
||||
(_text[_pos] != '[' && _text[_pos] != '{')) {
|
||||
auto macro = std::make_shared<Macro>(name);
|
||||
out.emplace_back(macro);
|
||||
return macro;
|
||||
}
|
||||
|
||||
const bool collapsed = _text[_pos] == '{';
|
||||
_pos++;
|
||||
std::deque<std::shared_ptr<Macro>> descendants;
|
||||
auto children = ParseList(descendants);
|
||||
_pos++;
|
||||
|
||||
auto group = Macro::CreateGroup(name, children);
|
||||
group->SetCollapsed(collapsed);
|
||||
out.emplace_back(group);
|
||||
out.insert(out.end(), descendants.begin(), descendants.end());
|
||||
return group;
|
||||
}
|
||||
|
||||
void SkipSpaces()
|
||||
{
|
||||
while (_pos < _text.size() && _text[_pos] == ' ') {
|
||||
_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
std::string _text;
|
||||
size_t _pos = 0;
|
||||
};
|
||||
|
||||
std::string describeChildren(const Macro *parent)
|
||||
{
|
||||
std::string result;
|
||||
for (const auto ¯o : GetTopLevelMacros()) {
|
||||
if (macro->Parent().get() != parent) {
|
||||
continue;
|
||||
}
|
||||
if (!result.empty()) {
|
||||
result += ", ";
|
||||
}
|
||||
result += macro->Name();
|
||||
if (!macro->IsGroup()) {
|
||||
continue;
|
||||
}
|
||||
const bool collapsed = macro->IsCollapsed();
|
||||
result += collapsed ? "{" : "[";
|
||||
result += describeChildren(macro.get());
|
||||
result += collapsed ? "}" : "]";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string describeMacros()
|
||||
{
|
||||
return describeChildren(nullptr);
|
||||
}
|
||||
|
||||
std::shared_ptr<Macro> getMacro(const std::string &name)
|
||||
{
|
||||
return GetWeakMacroByName(name.c_str()).lock();
|
||||
}
|
||||
|
||||
bool isHiddenByCollapsedAncestor(const Macro *macro)
|
||||
{
|
||||
for (auto parent = macro->Parent(); parent; parent = parent->Parent()) {
|
||||
@@ -148,8 +45,7 @@ public:
|
||||
_tree(new MacroTree(_window.get()))
|
||||
{
|
||||
GetMacroSearchSettings() = {};
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
macros = LayoutParser(layout).Parse();
|
||||
SetupMacros(layout);
|
||||
|
||||
// Same settings as in forms/advanced-scene-switcher.ui
|
||||
_tree->setDragEnabled(true);
|
||||
@@ -394,7 +290,7 @@ private:
|
||||
std::vector<Macro *> openGroups;
|
||||
for (const auto ¯o : macros) {
|
||||
INFO("macro \"" << macro->Name() << "\" in "
|
||||
<< describeMacros());
|
||||
<< DescribeMacros());
|
||||
const auto parent = macro->Parent().get();
|
||||
while (!openGroups.empty() &&
|
||||
openGroups.back() != parent) {
|
||||
@@ -413,20 +309,21 @@ private:
|
||||
continue;
|
||||
}
|
||||
INFO("group \"" << macro->Name() << "\" in "
|
||||
<< describeMacros());
|
||||
const auto children = std::count_if(
|
||||
<< DescribeMacros());
|
||||
const auto descendants = std::count_if(
|
||||
macros.begin(), macros.end(),
|
||||
[¯o](const std::shared_ptr<Macro> &m) {
|
||||
return m->Parent() == macro;
|
||||
return IsMacroInGroup(m.get(),
|
||||
macro.get());
|
||||
});
|
||||
REQUIRE(macro->GroupSize() == (uint32_t)children);
|
||||
REQUIRE(macro->GroupSize() == (uint32_t)descendants);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckRowWidget(const std::string &name) const
|
||||
{
|
||||
INFO("row \"" << name << "\" in " << describeMacros());
|
||||
auto macro = getMacro(name);
|
||||
INFO("row \"" << name << "\" in " << DescribeMacros());
|
||||
auto macro = GetMacro(name);
|
||||
REQUIRE(macro);
|
||||
auto widget = RowWidget(name);
|
||||
REQUIRE(widget);
|
||||
@@ -518,12 +415,12 @@ TEST_CASE("Macro tree expand and collapse", "[macro-tree]")
|
||||
SECTION("Expand checkbox")
|
||||
{
|
||||
f.ClickCheckBox(f.ExpandCheckBox("G1"));
|
||||
REQUIRE(getMacro("G1")->IsCollapsed());
|
||||
REQUIRE(GetMacro("G1")->IsCollapsed());
|
||||
f.CheckInvariants();
|
||||
REQUIRE(f.VisibleRows() == Names{"A", "G1", "D", "G2"});
|
||||
|
||||
f.ClickCheckBox(f.ExpandCheckBox("G2"));
|
||||
REQUIRE_FALSE(getMacro("G2")->IsCollapsed());
|
||||
REQUIRE_FALSE(GetMacro("G2")->IsCollapsed());
|
||||
f.CheckInvariants();
|
||||
REQUIRE(f.VisibleRows() == Names{"A", "G1", "D", "G2", "E"});
|
||||
|
||||
@@ -536,11 +433,11 @@ TEST_CASE("Macro tree expand and collapse", "[macro-tree]")
|
||||
SECTION("Double click")
|
||||
{
|
||||
DoubleClick(f.Viewport(), f.RowCenter("G2"));
|
||||
REQUIRE_FALSE(getMacro("G2")->IsCollapsed());
|
||||
REQUIRE_FALSE(GetMacro("G2")->IsCollapsed());
|
||||
f.CheckInvariants();
|
||||
|
||||
DoubleClick(f.Viewport(), f.RowCenter("G1"));
|
||||
REQUIRE(getMacro("G1")->IsCollapsed());
|
||||
REQUIRE(GetMacro("G1")->IsCollapsed());
|
||||
f.CheckInvariants();
|
||||
}
|
||||
|
||||
@@ -573,43 +470,43 @@ TEST_CASE("Macro tree add", "[macro-tree]")
|
||||
{
|
||||
f.Tree()->Add(newMacro);
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[B], C, G2{D}, X");
|
||||
REQUIRE(DescribeMacros() == "A, G1[B], C, G2{D}, X");
|
||||
REQUIRE(f.Tree()->GetCurrentMacro() == newMacro);
|
||||
}
|
||||
|
||||
SECTION("After top level macro")
|
||||
{
|
||||
f.Tree()->Add(newMacro, getMacro("A"));
|
||||
f.Tree()->Add(newMacro, GetMacro("A"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, X, G1[B], C, G2{D}");
|
||||
REQUIRE(DescribeMacros() == "A, X, G1[B], C, G2{D}");
|
||||
}
|
||||
|
||||
SECTION("After macro following collapsed group")
|
||||
{
|
||||
f.Tree()->Add(newMacro, getMacro("C"));
|
||||
f.Tree()->Add(newMacro, GetMacro("C"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[B], C, X, G2{D}");
|
||||
REQUIRE(DescribeMacros() == "A, G1[B], C, X, G2{D}");
|
||||
}
|
||||
|
||||
// Mirrors AdvSceneSwitcher::on_macroAdd_clicked() with a group selected
|
||||
SECTION("Into group")
|
||||
{
|
||||
auto group = getMacro("G2");
|
||||
auto group = GetMacro("G2");
|
||||
f.Tree()->ExpandGroup(group);
|
||||
Macro::PrepareMoveToGroup(group, newMacro);
|
||||
f.Tree()->AddToGroup(newMacro, group);
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[B], C, G2[X, D]");
|
||||
REQUIRE(DescribeMacros() == "A, G1[B], C, G2[X, D]");
|
||||
}
|
||||
|
||||
// Mirrors AdvSceneSwitcher::on_macroAdd_clicked() with a subitem
|
||||
// selected
|
||||
SECTION("After subitem")
|
||||
{
|
||||
Macro::PrepareMoveToGroup(getMacro("G1"), newMacro);
|
||||
f.Tree()->Add(newMacro, getMacro("B"));
|
||||
Macro::PrepareMoveToGroup(GetMacro("G1"), newMacro);
|
||||
f.Tree()->Add(newMacro, GetMacro("B"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[B, X], C, G2{D}");
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, X], C, G2{D}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,40 +516,40 @@ TEST_CASE("Macro tree remove", "[macro-tree]")
|
||||
|
||||
SECTION("Subitem")
|
||||
{
|
||||
f.Tree()->Remove(getMacro("B"));
|
||||
f.Tree()->Remove(GetMacro("B"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[C], D, G2{E, F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, G1[C], D, G2{E, F}, H");
|
||||
|
||||
f.Tree()->Remove(getMacro("C"));
|
||||
f.Tree()->Remove(GetMacro("C"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[], D, G2{E, F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, G1[], D, G2{E, F}, H");
|
||||
}
|
||||
|
||||
SECTION("Expanded group")
|
||||
{
|
||||
f.Tree()->Remove(getMacro("G1"));
|
||||
f.Tree()->Remove(GetMacro("G1"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, D, G2{E, F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, D, G2{E, F}, H");
|
||||
}
|
||||
|
||||
SECTION("Collapsed group")
|
||||
{
|
||||
f.Tree()->Remove(getMacro("G2"));
|
||||
f.Tree()->Remove(GetMacro("G2"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[B, C], D, H");
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, C], D, H");
|
||||
}
|
||||
|
||||
SECTION("Macro after collapsed group")
|
||||
{
|
||||
f.Tree()->Remove(getMacro("H"));
|
||||
f.Tree()->Remove(GetMacro("H"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[B, C], D, G2{E, F}");
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, C], D, G2{E, F}");
|
||||
}
|
||||
|
||||
SECTION("Everything")
|
||||
{
|
||||
for (const auto &name : {"H", "A", "G2", "B", "G1", "D"}) {
|
||||
f.Tree()->Remove(getMacro(name));
|
||||
f.Tree()->Remove(GetMacro(name));
|
||||
f.CheckInvariants();
|
||||
}
|
||||
REQUIRE(GetTopLevelMacros().empty());
|
||||
@@ -663,7 +560,7 @@ TEST_CASE("Macro tree remove", "[macro-tree]")
|
||||
f.ClickRow("D");
|
||||
f.Tree()->Remove(f.Tree()->GetCurrentMacro());
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[B, C], G2{E, F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, C], G2{E, F}, H");
|
||||
REQUIRE(f.Tree()->SelectionEmpty());
|
||||
}
|
||||
}
|
||||
@@ -672,80 +569,80 @@ TEST_CASE("Macro tree up and down", "[macro-tree]")
|
||||
{
|
||||
TreeFixture f("A, B, G1[C, D], E, G2{F}, H");
|
||||
auto up = [&](const char *name) {
|
||||
f.Tree()->Up(getMacro(name));
|
||||
f.Tree()->Up(GetMacro(name));
|
||||
f.CheckInvariants();
|
||||
};
|
||||
auto down = [&](const char *name) {
|
||||
f.Tree()->Down(getMacro(name));
|
||||
f.Tree()->Down(GetMacro(name));
|
||||
f.CheckInvariants();
|
||||
};
|
||||
|
||||
SECTION("Top level")
|
||||
{
|
||||
up("B");
|
||||
REQUIRE(describeMacros() == "B, A, G1[C, D], E, G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "B, A, G1[C, D], E, G2{F}, H");
|
||||
down("B");
|
||||
REQUIRE(describeMacros() == "A, B, G1[C, D], E, G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, B, G1[C, D], E, G2{F}, H");
|
||||
}
|
||||
|
||||
SECTION("First and last")
|
||||
{
|
||||
up("A");
|
||||
down("H");
|
||||
REQUIRE(describeMacros() == "A, B, G1[C, D], E, G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, B, G1[C, D], E, G2{F}, H");
|
||||
}
|
||||
|
||||
SECTION("Within group")
|
||||
{
|
||||
up("D");
|
||||
REQUIRE(describeMacros() == "A, B, G1[D, C], E, G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, B, G1[D, C], E, G2{F}, H");
|
||||
up("D");
|
||||
REQUIRE(describeMacros() == "A, B, G1[D, C], E, G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, B, G1[D, C], E, G2{F}, H");
|
||||
down("D");
|
||||
down("D");
|
||||
REQUIRE(describeMacros() == "A, B, G1[C, D], E, G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, B, G1[C, D], E, G2{F}, H");
|
||||
}
|
||||
|
||||
SECTION("Macro past expanded group")
|
||||
{
|
||||
down("B");
|
||||
REQUIRE(describeMacros() == "A, G1[C, D], B, E, G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, G1[C, D], B, E, G2{F}, H");
|
||||
up("E");
|
||||
up("E");
|
||||
REQUIRE(describeMacros() == "A, E, G1[C, D], B, G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, E, G1[C, D], B, G2{F}, H");
|
||||
}
|
||||
|
||||
SECTION("Macro past collapsed group")
|
||||
{
|
||||
down("E");
|
||||
REQUIRE(describeMacros() == "A, B, G1[C, D], G2{F}, E, H");
|
||||
REQUIRE(DescribeMacros() == "A, B, G1[C, D], G2{F}, E, H");
|
||||
up("E");
|
||||
up("H");
|
||||
REQUIRE(describeMacros() == "A, B, G1[C, D], E, H, G2{F}");
|
||||
REQUIRE(DescribeMacros() == "A, B, G1[C, D], E, H, G2{F}");
|
||||
}
|
||||
|
||||
SECTION("Expanded group")
|
||||
{
|
||||
down("G1");
|
||||
REQUIRE(describeMacros() == "A, B, E, G1[C, D], G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, B, E, G1[C, D], G2{F}, H");
|
||||
down("G1");
|
||||
REQUIRE(describeMacros() == "A, B, E, G2{F}, G1[C, D], H");
|
||||
REQUIRE(DescribeMacros() == "A, B, E, G2{F}, G1[C, D], H");
|
||||
up("G1");
|
||||
up("G1");
|
||||
up("G1");
|
||||
REQUIRE(describeMacros() == "A, G1[C, D], B, E, G2{F}, H");
|
||||
REQUIRE(DescribeMacros() == "A, G1[C, D], B, E, G2{F}, H");
|
||||
}
|
||||
|
||||
SECTION("Collapsed group")
|
||||
{
|
||||
up("G2");
|
||||
REQUIRE(describeMacros() == "A, B, G1[C, D], G2{F}, E, H");
|
||||
REQUIRE(DescribeMacros() == "A, B, G1[C, D], G2{F}, E, H");
|
||||
up("G2");
|
||||
REQUIRE(describeMacros() == "A, B, G2{F}, G1[C, D], E, H");
|
||||
REQUIRE(DescribeMacros() == "A, B, G2{F}, G1[C, D], E, H");
|
||||
down("G2");
|
||||
down("G2");
|
||||
down("G2");
|
||||
REQUIRE(describeMacros() == "A, B, G1[C, D], E, H, G2{F}");
|
||||
REQUIRE(DescribeMacros() == "A, B, G1[C, D], E, H, G2{F}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,7 +654,7 @@ TEST_CASE("Macro tree group and ungroup", "[macro-tree]")
|
||||
f.SelectRows({"B", "C"});
|
||||
f.Tree()->GroupSelectedItems();
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, " + newGroupName + "[B, C], D");
|
||||
REQUIRE(DescribeMacros() == "A, " + newGroupName + "[B, C], D");
|
||||
}
|
||||
|
||||
SECTION("Group non adjacent macros")
|
||||
@@ -766,7 +663,7 @@ TEST_CASE("Macro tree group and ungroup", "[macro-tree]")
|
||||
f.SelectRows({"D", "B"});
|
||||
f.Tree()->GroupSelectedItems();
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, " + newGroupName + "[B, D], C");
|
||||
REQUIRE(DescribeMacros() == "A, " + newGroupName + "[B, D], C");
|
||||
}
|
||||
|
||||
SECTION("Group after collapsed group")
|
||||
@@ -775,7 +672,7 @@ TEST_CASE("Macro tree group and ungroup", "[macro-tree]")
|
||||
f.SelectRows({"B", "C"});
|
||||
f.Tree()->GroupSelectedItems();
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() ==
|
||||
REQUIRE(DescribeMacros() ==
|
||||
"G1{X}, A, " + newGroupName + "[B, C]");
|
||||
}
|
||||
|
||||
@@ -785,7 +682,7 @@ TEST_CASE("Macro tree group and ungroup", "[macro-tree]")
|
||||
f.SelectRows({"G1"});
|
||||
f.Tree()->UngroupSelectedGroups();
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, B, C, D");
|
||||
REQUIRE(DescribeMacros() == "A, B, C, D");
|
||||
}
|
||||
|
||||
SECTION("Ungroup collapsed group")
|
||||
@@ -794,7 +691,7 @@ TEST_CASE("Macro tree group and ungroup", "[macro-tree]")
|
||||
f.SelectRows({"G1"});
|
||||
f.Tree()->UngroupSelectedGroups();
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, B, C, D");
|
||||
REQUIRE(DescribeMacros() == "A, B, C, D");
|
||||
REQUIRE(f.VisibleRows() == Names{"A", "B", "C", "D"});
|
||||
}
|
||||
|
||||
@@ -804,7 +701,7 @@ TEST_CASE("Macro tree group and ungroup", "[macro-tree]")
|
||||
f.SelectRows({"G1", "G2"});
|
||||
f.Tree()->UngroupSelectedGroups();
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, B, C, D");
|
||||
REQUIRE(DescribeMacros() == "A, B, C, D");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -821,7 +718,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("A"), f.RowBottom("C"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "B, C, A, D");
|
||||
REQUIRE(DescribeMacros() == "B, C, A, D");
|
||||
}
|
||||
|
||||
SECTION("Macro above macro")
|
||||
@@ -830,7 +727,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("D"), f.RowTop("A"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "D, A, B, C");
|
||||
REQUIRE(DescribeMacros() == "D, A, B, C");
|
||||
}
|
||||
|
||||
SECTION("Macro below last row")
|
||||
@@ -840,7 +737,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.Drag(f.RowCenter("A"),
|
||||
QPoint(40, f.Viewport()->height() - 5));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "B, C, A");
|
||||
REQUIRE(DescribeMacros() == "B, C, A");
|
||||
}
|
||||
|
||||
SECTION("Macro onto expanded group")
|
||||
@@ -849,7 +746,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("A"), f.RowCenter("G1"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "G1[A, B, C], D");
|
||||
REQUIRE(DescribeMacros() == "G1[A, B, C], D");
|
||||
}
|
||||
|
||||
SECTION("Macro between subitems")
|
||||
@@ -858,7 +755,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("D"), f.RowTop("C"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[B, D, C]");
|
||||
REQUIRE(DescribeMacros() == "A, G1[B, D, C]");
|
||||
}
|
||||
|
||||
SECTION("Subitem out of group")
|
||||
@@ -867,7 +764,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("B"), f.RowBottom("D"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1[C], D, B");
|
||||
REQUIRE(DescribeMacros() == "A, G1[C], D, B");
|
||||
}
|
||||
|
||||
SECTION("Subitem to top")
|
||||
@@ -876,7 +773,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("C"), f.RowTop("A"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "C, A, G1[B], D");
|
||||
REQUIRE(DescribeMacros() == "C, A, G1[B], D");
|
||||
}
|
||||
|
||||
SECTION("Macro past collapsed group")
|
||||
@@ -885,7 +782,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("D"), f.RowTop("A"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "D, A, G1{B, C}");
|
||||
REQUIRE(DescribeMacros() == "D, A, G1{B, C}");
|
||||
}
|
||||
|
||||
SECTION("Expanded group")
|
||||
@@ -894,7 +791,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("G1"), f.RowBottom("D"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, D, G1[B, C], E");
|
||||
REQUIRE(DescribeMacros() == "A, D, G1[B, C], E");
|
||||
}
|
||||
|
||||
SECTION("Collapsed group")
|
||||
@@ -903,7 +800,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("G1"), f.RowBottom("D"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, D, G1{B, C}, E");
|
||||
REQUIRE(DescribeMacros() == "A, D, G1{B, C}, E");
|
||||
}
|
||||
|
||||
SECTION("Group onto group is rejected")
|
||||
@@ -912,7 +809,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("G2"), f.RowCenter("G1"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "G1[A], G2[B]");
|
||||
REQUIRE(DescribeMacros() == "G1[A], G2[B]");
|
||||
}
|
||||
|
||||
SECTION("Multiple macros")
|
||||
@@ -922,7 +819,7 @@ TEST_CASE("Macro tree drag and drop", "[macro-tree]")
|
||||
f.SelectRows({"A", "C"});
|
||||
f.Drag(f.RowCenter("C"), f.RowBottom("D"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "B, D, A, C");
|
||||
REQUIRE(DescribeMacros() == "B, D, A, C");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -934,7 +831,7 @@ TEST_CASE("Macro tree drop onto collapsed group", "[macro-tree][!shouldfail]")
|
||||
f.IgnoreModelSignalErrors();
|
||||
f.Drag(f.RowCenter("D"), f.RowCenter("G1"));
|
||||
f.CheckInvariants();
|
||||
REQUIRE(describeMacros() == "A, G1{D, B, C}");
|
||||
REQUIRE(DescribeMacros() == "A, G1{D, B, C}");
|
||||
}
|
||||
|
||||
TEST_CASE("Macro tree search", "[macro-tree]")
|
||||
@@ -967,9 +864,9 @@ TEST_CASE("Macro tree search", "[macro-tree]")
|
||||
TreeFixture f("A1, G1[B1, C2], D2, E2");
|
||||
f.SetupSearch();
|
||||
f.Search("2");
|
||||
f.Tree()->Remove(getMacro("D2"));
|
||||
f.Tree()->Remove(GetMacro("D2"));
|
||||
f.CheckInvariants();
|
||||
f.Tree()->Up(getMacro("E2"));
|
||||
f.Tree()->Up(GetMacro("E2"));
|
||||
f.CheckInvariants();
|
||||
f.ClickCheckBox(f.ExpandCheckBox("G1"));
|
||||
f.CheckInvariants();
|
||||
@@ -991,9 +888,9 @@ TEST_CASE("Macro tree running state", "[macro-tree]")
|
||||
REQUIRE(groupCheckBox->checkState() == Qt::Checked);
|
||||
|
||||
f.ClickCheckBox(groupCheckBox);
|
||||
REQUIRE(getMacro("B")->Paused());
|
||||
REQUIRE(getMacro("C")->Paused());
|
||||
REQUIRE_FALSE(getMacro("A")->Paused());
|
||||
REQUIRE(GetMacro("B")->Paused());
|
||||
REQUIRE(GetMacro("C")->Paused());
|
||||
REQUIRE_FALSE(GetMacro("A")->Paused());
|
||||
REQUIRE(groupCheckBox->checkState() == Qt::Unchecked);
|
||||
f.CheckInvariants();
|
||||
}
|
||||
@@ -1002,7 +899,7 @@ TEST_CASE("Macro tree running state", "[macro-tree]")
|
||||
{
|
||||
TreeFixture f("A, G1[B, C]");
|
||||
f.ClickCheckBox(f.RunningCheckBox("B"));
|
||||
REQUIRE(getMacro("B")->Paused());
|
||||
REQUIRE(GetMacro("B")->Paused());
|
||||
REQUIRE(f.RunningCheckBox("G1")->checkState() ==
|
||||
Qt::PartiallyChecked);
|
||||
f.CheckInvariants();
|
||||
@@ -1011,7 +908,7 @@ TEST_CASE("Macro tree running state", "[macro-tree]")
|
||||
SECTION("Update after backend change")
|
||||
{
|
||||
TreeFixture f("G0{X}, A, B");
|
||||
getMacro("B")->SetPaused(true);
|
||||
GetMacro("B")->SetPaused(true);
|
||||
f.Tree()->UpdateRunningStates();
|
||||
f.CheckInvariants();
|
||||
}
|
||||
@@ -1024,14 +921,14 @@ TEST_CASE("Macro tree running state after collapsed group",
|
||||
{
|
||||
TreeFixture f("G0{X}, A, G1[B, C]");
|
||||
f.ClickCheckBox(f.RunningCheckBox("B"));
|
||||
REQUIRE(getMacro("B")->Paused());
|
||||
REQUIRE(GetMacro("B")->Paused());
|
||||
REQUIRE(f.RunningCheckBox("G1")->checkState() == Qt::PartiallyChecked);
|
||||
}
|
||||
|
||||
TEST_CASE("Macro tree rename", "[macro-tree]")
|
||||
{
|
||||
TreeFixture f("A, G1{B}, C");
|
||||
getMacro("C")->SetName("Z");
|
||||
GetMacro("C")->SetName("Z");
|
||||
emit MacroSignalManager::Instance()->Rename("C", "Z");
|
||||
f.CheckInvariants();
|
||||
REQUIRE(f.VisibleRows() == Names{"A", "G1", "Z"});
|
||||
|
||||
Reference in New Issue
Block a user