Files
wx_wherigo/libs/wxWidgets-3.3.1/tests/controls/treectrltest.cpp
2026-02-14 09:47:24 +01:00

706 lines
20 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: tests/controls/treectrltest.cpp
// Purpose: wxTreeCtrl unit test
// Author: Vadim Zeitlin
// Created: 2008-11-26
// Copyright: (c) 2008 Vadim Zeitlin <vadim@wxwidgets.org>
// (c) 2010 Steven Lamerton
///////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#include "testprec.h"
#if wxUSE_TREECTRL
#ifndef WX_PRECOMP
#include "wx/app.h"
#endif // WX_PRECOMP
#include "wx/artprov.h"
#include "wx/imaglist.h"
#include "wx/treectrl.h"
#include "wx/uiaction.h"
#include "testableframe.h"
#include "waitfor.h"
// ----------------------------------------------------------------------------
// test class
// ----------------------------------------------------------------------------
class TreeCtrlTestCase
{
public:
explicit TreeCtrlTestCase(int exStyle = 0)
{
m_tree = new wxTreeCtrl(wxTheApp->GetTopWindow(),
wxID_ANY,
wxDefaultPosition,
wxSize(400, 200),
wxTR_DEFAULT_STYLE | wxTR_EDIT_LABELS | exStyle);
m_root = m_tree->AddRoot("root");
m_child1 = m_tree->AppendItem(m_root, "child1");
m_child2 = m_tree->AppendItem(m_root, "child2");
m_grandchild = m_tree->AppendItem(m_child1, "grandchild");
m_tree->SetSize(400, 200);
m_tree->ExpandAll();
m_tree->Refresh();
m_tree->Update();
}
~TreeCtrlTestCase()
{
delete m_tree;
}
protected:
// the tree control itself
wxTreeCtrl *m_tree = nullptr;
// and some of its items
wxTreeItemId m_root,
m_child1,
m_child2,
m_grandchild;
wxDECLARE_NO_COPY_CLASS(TreeCtrlTestCase);
};
// Notice that toggling the wxTR_HIDE_ROOT window style with ToggleWindowStyle
// has no effect under wxMSW if wxTreeCtrl::AddRoot() has already been called.
// So we need this fixture (used by HasChildren and GetCount below) to create
// the wxTreeCtrl with this style before AddRoot() is called.
class TreeCtrlHideRootTestCase : public TreeCtrlTestCase
{
public:
TreeCtrlHideRootTestCase() : TreeCtrlTestCase(wxTR_HIDE_ROOT)
{
}
};
// ----------------------------------------------------------------------------
// the tests themselves
// ----------------------------------------------------------------------------
TEST_CASE_METHOD(TreeCtrlHideRootTestCase, "wxTreeCtrl::HasChildren", "[treectrl]")
{
CHECK( m_tree->HasChildren(m_root) );
CHECK( m_tree->HasChildren(m_child1) );
CHECK_FALSE( m_tree->HasChildren(m_child2) );
CHECK_FALSE( m_tree->HasChildren(m_grandchild) );
}
TEST_CASE_METHOD(TreeCtrlHideRootTestCase, "wxTreeCtrl::GetCount", "[treectrl]")
{
CHECK(m_tree->GetCount() == 3);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::SelectItemSingle", "[treectrl]")
{
// this test should be only ran in single-selection control
CHECK_FALSE( m_tree->HasFlag(wxTR_MULTIPLE) );
// initially nothing is selected
CHECK_FALSE( m_tree->IsSelected(m_child1) );
// selecting an item should make it selected
m_tree->SelectItem(m_child1);
CHECK( m_tree->IsSelected(m_child1) );
// selecting it again shouldn't change anything
m_tree->SelectItem(m_child1);
CHECK( m_tree->IsSelected(m_child1) );
// selecting another item should switch the selection to it
m_tree->SelectItem(m_child2);
CHECK_FALSE( m_tree->IsSelected(m_child1) );
CHECK( m_tree->IsSelected(m_child2) );
// selecting it again still shouldn't change anything
m_tree->SelectItem(m_child2);
CHECK_FALSE( m_tree->IsSelected(m_child1) );
CHECK( m_tree->IsSelected(m_child2) );
// deselecting an item should remove the selection entirely
m_tree->UnselectItem(m_child2);
CHECK_FALSE( m_tree->IsSelected(m_child1) );
CHECK_FALSE( m_tree->IsSelected(m_child2) );
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::SelectItemMulti", "[treectrl]")
{
// this test should be only ran in multi-selection control
m_tree->ToggleWindowStyle(wxTR_MULTIPLE);
// initially nothing is selected
CHECK_FALSE( m_tree->IsSelected(m_child1) );
// selecting an item should make it selected
m_tree->SelectItem(m_child1);
CHECK( m_tree->IsSelected(m_child1) );
// selecting it again shouldn't change anything
m_tree->SelectItem(m_child1);
CHECK( m_tree->IsSelected(m_child1) );
// selecting another item shouldn't deselect the previously selected one
m_tree->SelectItem(m_child2);
CHECK( m_tree->IsSelected(m_child1) );
CHECK( m_tree->IsSelected(m_child2) );
// selecting it again still shouldn't change anything
m_tree->SelectItem(m_child2);
CHECK( m_tree->IsSelected(m_child1) );
CHECK( m_tree->IsSelected(m_child2) );
// deselecting one of the items should leave the others selected
m_tree->UnselectItem(m_child1);
CHECK_FALSE( m_tree->IsSelected(m_child1) );
CHECK( m_tree->IsSelected(m_child2) );
// collapsing a branch with selected items should still leave them selected
m_tree->Expand(m_child1);
m_tree->SelectItem(m_grandchild);
CHECK( m_tree->IsSelected(m_grandchild) );
m_tree->Collapse(m_child1);
CHECK( m_tree->IsSelected(m_grandchild) );
m_tree->Expand(m_child1);
CHECK( m_tree->IsSelected(m_grandchild) );
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::DeleteItem", "[treectrl]")
{
EventCounter deleteitem(m_tree, wxEVT_TREE_DELETE_ITEM);
wxTreeItemId todelete = m_tree->AppendItem(m_root, "deleteme");
m_tree->AppendItem(todelete, "deleteme2");
m_tree->Delete(todelete);
CHECK(deleteitem.GetCount() == 2);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::DeleteChildren", "[treectrl]")
{
EventCounter deletechildren(m_tree, wxEVT_TREE_DELETE_ITEM);
m_tree->AppendItem(m_child1, "another grandchild");
m_tree->DeleteChildren(m_child1);
CHECK( deletechildren.GetCount() == 2 );
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::DeleteAllItems", "[treectrl]")
{
EventCounter deleteall(m_tree, wxEVT_TREE_DELETE_ITEM);
m_tree->DeleteAllItems();
CHECK( deleteall.GetCount() == 4 );
}
#if wxUSE_UIACTIONSIMULATOR
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::ItemClick", "[treectrl]")
{
EventCounter activated(m_tree, wxEVT_TREE_ITEM_ACTIVATED);
EventCounter rclick(m_tree, wxEVT_TREE_ITEM_RIGHT_CLICK);
wxUIActionSimulator sim;
wxRect pos;
m_tree->GetBoundingRect(m_child1, pos, true);
// We move in slightly so we are not on the edge
wxPoint point = m_tree->ClientToScreen(pos.GetPosition()) + wxPoint(4, 4);
sim.MouseMove(point);
wxYield();
sim.MouseDblClick();
wxYield();
sim.MouseClick(wxMOUSE_BTN_RIGHT);
wxYield();
CHECK(activated.GetCount() == 1);
CHECK(rclick.GetCount() == 1);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::LabelEdit", "[treectrl]")
{
EventCounter beginedit(m_tree, wxEVT_TREE_BEGIN_LABEL_EDIT);
EventCounter endedit(m_tree, wxEVT_TREE_END_LABEL_EDIT);
wxUIActionSimulator sim;
#ifdef __WXQT__
m_tree->SetFocus();
wxYield();
#endif
m_tree->SetFocusedItem(m_tree->GetRootItem());
m_tree->EditLabel(m_tree->GetRootItem());
sim.Text("newroottext");
wxYield();
CHECK(beginedit.GetCount() == 1);
sim.Char(WXK_RETURN);
wxYield();
CHECK(endedit.GetCount() == 1);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::KeyDown", "[treectrl]")
{
EventCounter keydown(m_tree, wxEVT_TREE_KEY_DOWN);
wxUIActionSimulator sim;
m_tree->SetFocus();
wxYield();
sim.Text("aAbB");
wxYield();
CHECK(keydown.GetCount() == 6);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::CollapseExpandEvents", "[treectrl]")
{
#ifdef __WXGTK__
// Works locally, but not when run on Travis CI.
if ( IsAutomaticTest() )
return;
#endif
m_tree->CollapseAll();
EventCounter collapsed(m_tree, wxEVT_TREE_ITEM_COLLAPSED);
EventCounter collapsing(m_tree, wxEVT_TREE_ITEM_COLLAPSING);
EventCounter expanded(m_tree, wxEVT_TREE_ITEM_EXPANDED);
EventCounter expanding(m_tree, wxEVT_TREE_ITEM_EXPANDING);
wxUIActionSimulator sim;
wxRect pos;
m_tree->GetBoundingRect(m_root, pos, true);
// We move in slightly so we are not on the edge
wxPoint point = m_tree->ClientToScreen(pos.GetPosition()) + wxPoint(4, 4);
sim.MouseMove(point);
wxYield();
sim.MouseDblClick();
wxYield();
CHECK(expanding.GetCount() == 1);
CHECK(expanded.GetCount() == 1);
#ifdef __WXGTK__
// Don't even know the reason why, but GTK has to sleep
// no less than 1200 for the test case to succeed.
wxMilliSleep(1200);
#endif
sim.MouseDblClick();
wxYield();
CHECK(collapsing.GetCount() == 1);
CHECK(collapsed.GetCount() == 1);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::SelectionChange", "[treectrl]")
{
m_tree->ExpandAll();
// This is currently needed to work around a problem under wxMSW: clicking
// on an item in an unfocused control generates two selection change events
// because of the SetFocus() call in TVN_SELCHANGED handler in wxMSW code.
// This is, of course, wrong on its own, but fixing it without breaking
// anything else is non-obvious, so for now at least work around this
// problem in the test.
m_tree->SetFocus();
bool vetoChange = false;
SECTION("Without veto selection change"){ }
SECTION("With veto selection change")
{
vetoChange = true;
}
int changed = 0;
int changing = 0;
auto handler = [&](wxTreeEvent& event)
{
const auto eventType = event.GetEventType();
if ( eventType == wxEVT_TREE_SEL_CHANGING )
{
++changing;
if ( vetoChange && changed == 1 )
{
event.Veto();
}
}
else if ( eventType == wxEVT_TREE_SEL_CHANGED )
{
++changed;
}
};
m_tree->Bind(wxEVT_TREE_SEL_CHANGED, handler);
m_tree->Bind(wxEVT_TREE_SEL_CHANGING, handler);
wxUIActionSimulator sim;
wxRect poschild1, poschild2;
m_tree->GetBoundingRect(m_child1, poschild1, true);
m_tree->GetBoundingRect(m_child2, poschild2, true);
// We move in slightly so we are not on the edge
wxPoint point1 = m_tree->ClientToScreen(poschild1.GetPosition()) + wxPoint(4, 4);
wxPoint point2 = m_tree->ClientToScreen(poschild2.GetPosition()) + wxPoint(4, 4);
sim.MouseMove(point1);
wxYield();
sim.MouseClick();
wxYield();
CHECK(changed == 1);
CHECK(changing == 1);
sim.MouseMove(point2);
wxYield();
sim.MouseClick();
wxYield();
CHECK(changed == (vetoChange ? 1 : 2));
CHECK(changing == 2);
if ( vetoChange )
{
CHECK( m_tree->IsSelected(m_child1) );
CHECK_FALSE( m_tree->IsSelected(m_child2) );
}
else
{
CHECK_FALSE( m_tree->IsSelected(m_child1) );
CHECK( m_tree->IsSelected(m_child2) );
}
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::SelectItemMultiInteractive", "[treectrl]")
{
#if defined(__WXGTK__) && !defined(__WXGTK3__)
// FIXME: This test fails on GitHub CI under wxGTK2 although works fine on
// development machine, no idea why though!
if ( IsAutomaticTest() )
return;
#endif // wxGTK2
// this test should be only ran in multi-selection control
m_tree->ToggleWindowStyle(wxTR_MULTIPLE);
m_tree->ExpandAll();
// This is currently needed to work around a problem under wxMSW: clicking
// on an item in an unfocused control generates two selection change events
// because of the SetFocus() call in TVN_SELCHANGED handler in wxMSW code.
// This is, of course, wrong on its own, but fixing it without breaking
// anything else is non-obvious, so for now at least work around this
// problem in the test.
m_tree->SetFocus();
EventCounter beginedit(m_tree, wxEVT_TREE_BEGIN_LABEL_EDIT);
wxUIActionSimulator sim;
wxRect poschild1, poschild2;
m_tree->GetBoundingRect(m_child1, poschild1, true);
m_tree->GetBoundingRect(m_child2, poschild2, true);
// We move in slightly so we are not on the edge
wxPoint point1 = m_tree->ClientToScreen(poschild1.GetPosition()) + wxPoint(4, 4);
wxPoint point2 = m_tree->ClientToScreen(poschild2.GetPosition()) + wxPoint(4, 4);
sim.MouseMove(point1);
wxYield();
sim.MouseClick();
wxYield();
sim.MouseMove(point2);
wxYield();
sim.KeyDown(WXK_CONTROL);
sim.MouseClick();
sim.KeyUp(WXK_CONTROL);
wxYield();
// m_child1 and m_child2 should be selected.
CHECK( m_tree->IsSelected(m_child1) );
CHECK( m_tree->IsSelected(m_child2) );
CHECK( beginedit.GetCount() == 0 );
// Time needed (in ms) for the editor to display. The test will not pass
// if the value is less than 400, 510, 800 under wxQt, wxGTK, wxMSW resp.
const int BEGIN_EDIT_TIMEOUT = 800;
YieldForAWhile(BEGIN_EDIT_TIMEOUT);
sim.MouseClick();
YieldForAWhile(BEGIN_EDIT_TIMEOUT);
// Only m_child2 should be selected now.
CHECK_FALSE( m_tree->IsSelected(m_child1) );
CHECK( m_tree->IsSelected(m_child2) );
CHECK( beginedit.GetCount() == 0 ); // No editing should take place in the event of deselection.
sim.MouseClick();
YieldForAWhile(BEGIN_EDIT_TIMEOUT);
CHECK( beginedit.GetCount() == 1 ); // Start editing as usual.
sim.Char(WXK_RETURN); // End editing and close the editor.
wxYield();
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::Menu", "[treectrl]")
{
EventCounter menu(m_tree, wxEVT_TREE_ITEM_MENU);
wxUIActionSimulator sim;
wxRect pos;
m_tree->GetBoundingRect(m_child1, pos, true);
// We move in slightly so we are not on the edge
wxPoint point = m_tree->ClientToScreen(pos.GetPosition()) + wxPoint(4, 4);
sim.MouseMove(point);
wxYield();
sim.MouseClick(wxMOUSE_BTN_RIGHT);
wxYield();
CHECK(menu.GetCount() == 1);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::KeyNavigation", "[treectrl]")
{
wxUIActionSimulator sim;
m_tree->CollapseAll();
m_tree->SelectItem(m_root);
wxYield();
m_tree->SetFocus();
sim.Char(WXK_RIGHT);
wxYield();
CHECK(m_tree->IsExpanded(m_root));
#ifdef wxHAS_GENERIC_TREECTRL
sim.Char('-');
#else
sim.Char(WXK_LEFT);
#endif
wxYield();
CHECK(!m_tree->IsExpanded(m_root));
wxYield();
sim.Char(WXK_RIGHT);
sim.Char(WXK_DOWN);
wxYield();
CHECK(m_tree->GetSelection() == m_child1);
sim.Char(WXK_DOWN);
wxYield();
CHECK(m_tree->GetSelection() == m_child2);
}
#endif // wxUSE_UIACTIONSIMULATOR
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::ItemData", "[treectrl]")
{
wxTreeItemData* child1data = new wxTreeItemData();
wxTreeItemData* appenddata = new wxTreeItemData();
wxTreeItemData* insertdata = new wxTreeItemData();
m_tree->SetItemData(m_child1, child1data);
CHECK(m_tree->GetItemData(m_child1) == child1data);
CHECK(child1data->GetId() == m_child1);
wxTreeItemId append = m_tree->AppendItem(m_root, "new", -1, -1, appenddata);
CHECK(m_tree->GetItemData(append) == appenddata);
CHECK(appenddata->GetId() == append);
wxTreeItemId insert = m_tree->InsertItem(m_root, m_child1, "new", -1, -1,
insertdata);
CHECK(m_tree->GetItemData(insert) == insertdata);
CHECK(insertdata->GetId() == insert);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::Iteration", "[treectrl]")
{
// Get first / next / last child
wxTreeItemIdValue cookie;
CHECK(m_tree->GetFirstChild(m_root, cookie) == m_child1);
CHECK(m_tree->GetNextChild(m_root, cookie) == m_tree->GetLastChild(m_root));
CHECK(m_tree->GetLastChild(m_root) == m_child2);
// Get next / previous sibling
CHECK(m_tree->GetNextSibling(m_child1) == m_child2);
CHECK(m_tree->GetPrevSibling(m_child2) == m_child1);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::Parent", "[treectrl]")
{
CHECK(m_tree->GetRootItem() == m_root);
CHECK(m_tree->GetItemParent(m_child1) == m_root);
CHECK(m_tree->GetItemParent(m_child2) == m_root);
CHECK(m_tree->GetItemParent(m_grandchild) == m_child1);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::CollapseExpand", "[treectrl]")
{
m_tree->ExpandAll();
CHECK(m_tree->IsExpanded(m_root));
CHECK(m_tree->IsExpanded(m_child1));
m_tree->CollapseAll();
CHECK_FALSE(m_tree->IsExpanded(m_root));
CHECK_FALSE(m_tree->IsExpanded(m_child1));
m_tree->ExpandAllChildren(m_root);
CHECK(m_tree->IsExpanded(m_root));
CHECK(m_tree->IsExpanded(m_child1));
m_tree->CollapseAllChildren(m_child1);
CHECK_FALSE(m_tree->IsExpanded(m_child1));
m_tree->Expand(m_child1);
CHECK(m_tree->IsExpanded(m_child1));
m_tree->Collapse(m_root);
CHECK_FALSE(m_tree->IsExpanded(m_root));
CHECK(m_tree->IsExpanded(m_child1));
m_tree->CollapseAndReset(m_root);
CHECK_FALSE(m_tree->IsExpanded(m_root));
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::AssignImageList", "[treectrl]")
{
wxSize size(16, 16);
wxImageList *imagelist = new wxImageList(size.x, size.y);
imagelist->Add(wxArtProvider::GetIcon(wxART_QUESTION, wxART_OTHER, size));
wxImageList *statelist = new wxImageList(size.x, size.y);
statelist->Add(wxArtProvider::GetIcon(wxART_ERROR, wxART_OTHER, size));
m_tree->AssignImageList(imagelist);
m_tree->AssignStateImageList(statelist);
CHECK(m_tree->GetImageList() == imagelist);
CHECK(m_tree->GetStateImageList() == statelist);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::Focus", "[treectrl]")
{
m_tree->SetFocusedItem(m_child1);
CHECK(m_tree->GetFocusedItem() == m_child1);
m_tree->ClearFocusedItem();
CHECK_FALSE(m_tree->GetFocusedItem());
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::Bold", "[treectrl]")
{
CHECK_FALSE(m_tree->IsBold(m_child1));
m_tree->SetItemBold(m_child1);
CHECK(m_tree->IsBold(m_child1));
m_tree->SetItemBold(m_child1, false);
CHECK_FALSE(m_tree->IsBold(m_child1));
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::Visible", "[treectrl]")
{
m_tree->CollapseAll();
CHECK(m_tree->IsVisible(m_root));
CHECK_FALSE(m_tree->IsVisible(m_child1));
m_tree->EnsureVisible(m_grandchild);
CHECK(m_tree->IsVisible(m_grandchild));
m_tree->ExpandAll();
CHECK(m_tree->GetFirstVisibleItem() == m_root);
CHECK(m_tree->GetNextVisible(m_root) == m_child1);
CHECK(m_tree->GetNextVisible(m_child1) == m_grandchild);
CHECK(m_tree->GetNextVisible(m_grandchild) == m_child2);
CHECK_FALSE(m_tree->GetNextVisible(m_child2));
CHECK_FALSE(m_tree->GetPrevVisible(m_root));
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::Scroll", "[treectrl]")
{
// This trivial test just checks that calling ScrollTo() with the root item
// doesn't crash any longer, as it used to do when the root item was hidden.
m_tree->ScrollTo(m_root);
}
TEST_CASE_METHOD(TreeCtrlTestCase, "wxTreeCtrl::Sort", "[treectrl]")
{
wxTreeItemId zitem = m_tree->AppendItem(m_root, "zzzz");
wxTreeItemId aitem = m_tree->AppendItem(m_root, "aaaa");
m_tree->SortChildren(m_root);
wxTreeItemIdValue cookie;
CHECK(m_tree->GetFirstChild(m_root, cookie) == aitem);
CHECK(m_tree->GetNextChild(m_root, cookie) == m_child1);
CHECK(m_tree->GetNextChild(m_root, cookie) == m_child2);
CHECK(m_tree->GetNextChild(m_root, cookie) == zitem);
}
#endif //wxUSE_TREECTRL