Files
MARTe-Integrated-Components/Client/udpscope/tests/PaneTreeTest.cpp
T
Martino FerrariandClaude Opus 4.6 ea9689591d test: add coverage for closeLeaf's two untested branches
Gap 1: closeLeaf was only tested closing the first sibling; added test
closing the second sibling to cover the else branch of parent->a.get()==leaf.
The test verifies the correct sibling survives with its signal intact.

Gap 2: closeLeaf was only tested at depth 1 (root's direct children);
added test with depth-2 leaf (in right subtree) to exercise findParent's
recursive search in both subtrees. Tests that the correct leaf is promoted
and remaining signals are preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-27 19:46:29 +02:00

231 lines
8.0 KiB
C++

#include "PaneTree.h"
#include <gtest/gtest.h>
using namespace udpscope;
namespace {
const Rect kScreen{0.0, 0.0, 1000.0, 600.0};
std::vector<PaneTree::Placed> leavesOf(const PaneTree& tree, const Rect& area) {
std::vector<PaneTree::Placed> leaves;
std::vector<PaneTree::Splitter> splitters;
tree.layout(area, leaves, splitters);
return leaves;
}
} /* namespace */
TEST(PaneTree, StartsAsOneEmptyLeafFillingTheArea) {
PaneTree tree;
EXPECT_EQ(tree.leafCount(), 1u);
const auto leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 1u);
EXPECT_DOUBLE_EQ(leaves[0].rect.w, 1000.0);
EXPECT_DOUBLE_EQ(leaves[0].rect.h, 600.0);
EXPECT_TRUE(leaves[0].leaf->signals.empty());
}
TEST(PaneTree, SplittingIntoColumnsHalvesTheWidth) {
PaneTree tree;
tree.splitLeaf(tree.root(), Orient::Columns);
const auto leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 2u);
EXPECT_DOUBLE_EQ(leaves[0].rect.w, 500.0);
EXPECT_DOUBLE_EQ(leaves[1].rect.w, 500.0);
EXPECT_DOUBLE_EQ(leaves[0].rect.h, 600.0);
EXPECT_DOUBLE_EQ(leaves[1].rect.x, 500.0);
}
TEST(PaneTree, SplittingIntoRowsHalvesTheHeight) {
PaneTree tree;
tree.splitLeaf(tree.root(), Orient::Rows);
const auto leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 2u);
EXPECT_DOUBLE_EQ(leaves[0].rect.h, 300.0);
EXPECT_DOUBLE_EQ(leaves[1].rect.y, 300.0);
EXPECT_DOUBLE_EQ(leaves[0].rect.w, 1000.0);
}
// The pane being split keeps its content; the new pane is the empty one.
TEST(PaneTree, SplitKeepsTheOriginalContentInTheFirstChild) {
PaneTree tree;
tree.root()->signals.push_back(Assignment{"Voltage", Color{}, 1.5f, VScale{}});
tree.splitLeaf(tree.root(), Orient::Columns);
const auto leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 2u);
ASSERT_EQ(leaves[0].leaf->signals.size(), 1u);
EXPECT_EQ(leaves[0].leaf->signals[0].signalName, "Voltage");
EXPECT_TRUE(leaves[1].leaf->signals.empty());
}
TEST(PaneTree, ClosingALeafGivesItsSpaceToTheSibling) {
PaneTree tree;
tree.splitLeaf(tree.root(), Orient::Columns);
auto leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 2u);
leaves[1].leaf->signals.push_back(Assignment{"Keep", Color{}, 1.5f, VScale{}});
tree.closeLeaf(leaves[0].leaf);
EXPECT_EQ(tree.leafCount(), 1u);
leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 1u);
EXPECT_DOUBLE_EQ(leaves[0].rect.w, 1000.0);
ASSERT_EQ(leaves[0].leaf->signals.size(), 1u);
EXPECT_EQ(leaves[0].leaf->signals[0].signalName, "Keep");
}
TEST(PaneTree, RefusesToCloseTheLastLeaf) {
PaneTree tree;
tree.closeLeaf(tree.root());
EXPECT_EQ(tree.leafCount(), 1u);
}
// A pane in the middle of a 3x3 touches no window edge. It must still be
// splittable, which is why handles are inset inside the pane rather than
// keyed on the window border.
TEST(PaneTree, AnInteriorPaneIsStillSplittable) {
PaneTree tree;
tree.splitLeaf(tree.root(), Orient::Rows); // top / bottom
auto leaves = leavesOf(tree, kScreen);
tree.splitLeaf(leaves[1].leaf, Orient::Rows); // 3 rows
leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 3u);
PaneNode* middle = leaves[1].leaf;
tree.splitLeaf(middle, Orient::Columns);
leaves = leavesOf(tree, kScreen);
tree.splitLeaf(leaves[2].leaf, Orient::Columns);
EXPECT_EQ(tree.leafCount(), 5u);
}
TEST(PaneTree, LayoutReportsOneSplitterPerSplitNode) {
PaneTree tree;
tree.splitLeaf(tree.root(), Orient::Columns);
auto leaves = leavesOf(tree, kScreen);
tree.splitLeaf(leaves[0].leaf, Orient::Rows);
std::vector<PaneTree::Placed> out;
std::vector<PaneTree::Splitter> splitters;
tree.layout(kScreen, out, splitters);
EXPECT_EQ(out.size(), 3u);
EXPECT_EQ(splitters.size(), 2u);
}
TEST(PaneTree, RatioSurvivesALayoutRoundTrip) {
PaneTree tree;
tree.splitLeaf(tree.root(), Orient::Columns);
tree.setRatio(tree.root(), 0.25);
const auto leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 2u);
EXPECT_DOUBLE_EQ(leaves[0].rect.w, 250.0);
EXPECT_DOUBLE_EQ(leaves[1].rect.w, 750.0);
}
TEST(PaneTree, RatioIsClampedSoNeitherPaneGoesBelowTheMinimum) {
PaneTree tree;
tree.splitLeaf(tree.root(), Orient::Columns);
tree.setRatio(tree.root(), 0.001);
const auto leaves = leavesOf(tree, kScreen);
EXPECT_GE(leaves[0].rect.w, kMinPaneSize);
EXPECT_GE(leaves[1].rect.w, kMinPaneSize);
}
TEST(PaneTree, HitTestFindsTheSplitterBetweenTwoPanes) {
PaneTree tree;
tree.splitLeaf(tree.root(), Orient::Columns);
std::vector<PaneTree::Placed> leaves;
std::vector<PaneTree::Splitter> splitters;
tree.layout(kScreen, leaves, splitters);
ASSERT_EQ(splitters.size(), 1u);
const PaneTree::Splitter* hit = tree.hitTestSplitter(splitters, 500.0, 300.0);
ASSERT_NE(hit, nullptr);
EXPECT_EQ(hit->orient, Orient::Columns);
EXPECT_EQ(tree.hitTestSplitter(splitters, 100.0, 300.0), nullptr);
}
TEST(PaneTree, HitTestFindsInsetSplitHandlesAndTheCloseButton) {
const Rect pane{0.0, 0.0, 400.0, 300.0};
EXPECT_EQ(PaneTree::hitTestHandle(pane, 8.0, 150.0), Handle::Left);
EXPECT_EQ(PaneTree::hitTestHandle(pane, 392.0, 150.0), Handle::Right);
EXPECT_EQ(PaneTree::hitTestHandle(pane, 200.0, 8.0), Handle::Top);
EXPECT_EQ(PaneTree::hitTestHandle(pane, 200.0, 292.0), Handle::Bottom);
EXPECT_EQ(PaneTree::hitTestHandle(pane, 392.0, 8.0), Handle::Close);
EXPECT_EQ(PaneTree::hitTestHandle(pane, 200.0, 150.0), Handle::None);
}
// Gap 1: closeLeaf only tested with first child closed; test closing the second child.
TEST(PaneTree, ClosingTheSecondLeafPreservesTheFirstLeafContent) {
PaneTree tree;
tree.splitLeaf(tree.root(), Orient::Columns);
auto leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 2u);
// Assign distinct signals to each leaf
leaves[0].leaf->signals.push_back(Assignment{"Signal_A", Color{}, 1.5f, VScale{}});
leaves[1].leaf->signals.push_back(Assignment{"Signal_B", Color{}, 1.5f, VScale{}});
// Close the second leaf; the first should survive with its content
tree.closeLeaf(leaves[1].leaf);
EXPECT_EQ(tree.leafCount(), 1u);
leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 1u);
ASSERT_EQ(leaves[0].leaf->signals.size(), 1u);
EXPECT_EQ(leaves[0].leaf->signals[0].signalName, "Signal_A");
}
// Gap 2: closeLeaf only tested at depth 1; test at depth 2 (deeper recursion in findParent).
TEST(PaneTree, ClosingALeafAtDepth2PreservesOthersAndUpdatesCount) {
PaneTree tree;
// Build tree: split root (a, b), split b to get depth-2 leaf in the RIGHT subtree
tree.splitLeaf(tree.root(), Orient::Columns); // depth 1: root splits into a, b
auto leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 2u);
tree.splitLeaf(leaves[1].leaf, Orient::Rows); // depth 2: b splits into b.a, b.b
leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 3u);
// Assign distinct signals to each of the three leaves
leaves[0].leaf->signals.push_back(Assignment{"Depth1_Left", Color{}, 1.5f, VScale{}});
leaves[1].leaf->signals.push_back(Assignment{"Depth2_TopRight", Color{}, 1.5f, VScale{}});
leaves[2].leaf->signals.push_back(Assignment{"Depth2_BottomRight", Color{}, 1.5f, VScale{}});
// Close the first depth-2 leaf (leaves[1], which is in the right subtree)
tree.closeLeaf(leaves[1].leaf);
EXPECT_EQ(tree.leafCount(), 2u);
leaves = leavesOf(tree, kScreen);
ASSERT_EQ(leaves.size(), 2u);
// Verify the surviving depth-2 leaf has its signal intact
bool found_left = false;
bool found_bottom_right = false;
for (const auto& leaf : leaves) {
ASSERT_EQ(leaf.leaf->signals.size(), 1u);
if (leaf.leaf->signals[0].signalName == "Depth1_Left") {
found_left = true;
}
if (leaf.leaf->signals[0].signalName == "Depth2_BottomRight") {
found_bottom_right = true;
}
}
EXPECT_TRUE(found_left);
EXPECT_TRUE(found_bottom_right);
}