wxWidgets/tests/controls/dataviewctrltest.cpp
Ilya Sinitsyn 0144608b4b Test DVC with the custom model
wxDataViewCtrl::AssociateModel() don't create child nodes, which leads to
missing nodes if then used wxDataViewModel::ItemAdded(). So add the test
for such situation.
2022-03-17 08:10:58 +07:00

758 lines
21 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: tests/controls/dataviewctrltest.cpp
// Purpose: wxDataViewCtrl unit test
// Author: Vaclav Slavik
// Created: 2011-08-08
// Copyright: (c) 2011 Vaclav Slavik <vslavik@gmail.com>
///////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#include "testprec.h"
#if wxUSE_DATAVIEWCTRL
#include "wx/app.h"
#include "wx/dataview.h"
#include "wx/uiaction.h"
#ifdef __WXGTK__
#include "wx/stopwatch.h"
#endif // __WXGTK__
#include "testableframe.h"
#include "asserthelper.h"
// ----------------------------------------------------------------------------
// test class
// ----------------------------------------------------------------------------
class DataViewCtrlTestCase
{
public:
explicit DataViewCtrlTestCase(long style);
~DataViewCtrlTestCase();
protected:
void TestSelectionFor0and1();
// the dataview control itself
wxDataViewTreeCtrl *m_dvc;
// and some of its items
wxDataViewItem m_root,
m_child1,
m_child2,
m_grandchild;
wxDECLARE_NO_COPY_CLASS(DataViewCtrlTestCase);
};
class SingleSelectDataViewCtrlTestCase : public DataViewCtrlTestCase
{
public:
SingleSelectDataViewCtrlTestCase()
: DataViewCtrlTestCase(wxDV_SINGLE)
{
}
};
class MultiSelectDataViewCtrlTestCase : public DataViewCtrlTestCase
{
public:
MultiSelectDataViewCtrlTestCase()
: DataViewCtrlTestCase(wxDV_MULTIPLE)
{
}
};
class MultiColumnsDataViewCtrlTestCase
{
public:
MultiColumnsDataViewCtrlTestCase();
~MultiColumnsDataViewCtrlTestCase();
protected:
// the dataview control itself
wxDataViewListCtrl *m_dvc;
// constants
const wxSize m_size;
const int m_firstColumnWidth;
// and the columns
wxDataViewColumn* m_firstColumn;
wxDataViewColumn* m_lastColumn;
wxDECLARE_NO_COPY_CLASS(MultiColumnsDataViewCtrlTestCase);
};
class DataViewCtrlTestModel: public wxDataViewModel
{
public:
// Items of the model.
//
// wxTEST_ITEM_NULL
// |
// |-- wxTEST_ITEM_ROOT
// |
// |-- wxTEST_ITEM_CHILD
// |
// |-- wxTEST_ITEM_GRANDCHILD
// |
// |-- wxTEST_ITEM_GRANDCHILD_HIDDEN
//
enum wxTestItem
{
wxTEST_ITEM_NULL,
wxTEST_ITEM_ROOT,
wxTEST_ITEM_CHILD,
wxTEST_ITEM_GRANDCHILD,
wxTEST_ITEM_GRANDCHILD_HIDDEN,
};
DataViewCtrlTestModel()
: m_root(wxTEST_ITEM_ROOT),
m_child(wxTEST_ITEM_CHILD),
m_grandChild(wxTEST_ITEM_GRANDCHILD),
m_grandChildHidden(wxTEST_ITEM_GRANDCHILD_HIDDEN),
m_secondGrandchildVisible(false)
{
}
wxDataViewItem GetDataViewItem(wxTestItem item) const
{
switch( item )
{
case wxTEST_ITEM_NULL:
return wxDataViewItem();
case wxTEST_ITEM_ROOT:
return wxDataViewItem(const_cast<wxTestItem*>(&m_root));
case wxTEST_ITEM_CHILD:
return wxDataViewItem(const_cast<wxTestItem*>(&m_child));
case wxTEST_ITEM_GRANDCHILD:
return wxDataViewItem(const_cast<wxTestItem*>(&m_grandChild));
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
return wxDataViewItem(const_cast<wxTestItem*>(&m_grandChildHidden));
}
return wxDataViewItem();
}
// Overridden wxDataViewModel methods.
unsigned int GetColumnCount() const wxOVERRIDE
{
return 1;
}
wxString GetColumnType(unsigned int WXUNUSED(col)) const wxOVERRIDE
{
return "string";
}
void GetValue(wxVariant &variant, const wxDataViewItem &item,
unsigned int WXUNUSED(col)) const wxOVERRIDE
{
switch( GetItemID(item) )
{
case wxTEST_ITEM_NULL:
break;
case wxTEST_ITEM_ROOT:
variant = "root";
break;
case wxTEST_ITEM_CHILD:
variant = "child";
break;
case wxTEST_ITEM_GRANDCHILD:
variant = "grand child";
break;
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
variant = "initially hidden";
break;
}
}
bool SetValue(const wxVariant &WXUNUSED(variant),
const wxDataViewItem &WXUNUSED(item),
unsigned int WXUNUSED(col)) wxOVERRIDE
{
return false;
}
bool HasContainerColumns(const wxDataViewItem &WXUNUSED(item)) const wxOVERRIDE
{
// Always display all the columns, even for the containers.
return true;
}
wxDataViewItem GetParent(const wxDataViewItem &item) const wxOVERRIDE
{
switch( GetItemID(item) )
{
case wxTEST_ITEM_NULL:
FAIL( "The item is the top most container" );
return wxDataViewItem();
case wxTEST_ITEM_ROOT:
return wxDataViewItem();
case wxTEST_ITEM_CHILD:
return GetDataViewItem(m_root);
case wxTEST_ITEM_GRANDCHILD:
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
return GetDataViewItem(m_child);
}
return wxDataViewItem();
}
bool IsContainer(const wxDataViewItem &item) const wxOVERRIDE
{
switch( GetItemID(item) )
{
case wxTEST_ITEM_NULL:
case wxTEST_ITEM_ROOT:
case wxTEST_ITEM_CHILD:
return true;
case wxTEST_ITEM_GRANDCHILD:
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
return false;
}
return false;
}
unsigned int GetChildren(const wxDataViewItem &item,
wxDataViewItemArray &children) const wxOVERRIDE
{
switch( GetItemID(item) )
{
case wxTEST_ITEM_NULL:
children.push_back(GetDataViewItem(m_root));
return 1;
case wxTEST_ITEM_ROOT:
children.push_back(GetDataViewItem(m_child));
return 1;
case wxTEST_ITEM_CHILD:
children.push_back(GetDataViewItem(m_grandChild));
if ( m_secondGrandchildVisible )
{
children.push_back(GetDataViewItem(m_grandChildHidden));
return 2;
}
return 1;
case wxTEST_ITEM_GRANDCHILD:
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
FAIL( "The item is not a container" );
return 0;
}
return 0;
}
void ShowSecondGrandChild()
{
m_secondGrandchildVisible = true;
ItemAdded(GetDataViewItem(m_child), GetDataViewItem(m_grandChildHidden));
}
private:
wxTestItem GetItemID(const wxDataViewItem &dataViewItem) const
{
if ( dataViewItem.GetID() == NULL )
return wxTEST_ITEM_NULL;
return *static_cast<wxTestItem*>(dataViewItem.GetID());
}
wxTestItem m_root;
wxTestItem m_child;
wxTestItem m_grandChild;
wxTestItem m_grandChildHidden;
// Whether wxTEST_ITEM_GRANDCHILD_HIDDEN item should be visible or not.
bool m_secondGrandchildVisible;
};
class DataViewCtrlWithCustomModelTestCase
{
public:
DataViewCtrlWithCustomModelTestCase();
~DataViewCtrlWithCustomModelTestCase();
protected:
// The dataview control.
wxDataViewCtrl *m_dvc;
// The dataview model.
DataViewCtrlTestModel *m_model;
// Its items.
wxDataViewItem m_root, m_child, m_grandchild, m_grandchildHidden;
wxDECLARE_NO_COPY_CLASS(DataViewCtrlWithCustomModelTestCase);
};
// ----------------------------------------------------------------------------
// test initialization
// ----------------------------------------------------------------------------
DataViewCtrlTestCase::DataViewCtrlTestCase(long style)
{
m_dvc = new wxDataViewTreeCtrl(wxTheApp->GetTopWindow(),
wxID_ANY,
wxDefaultPosition,
wxSize(400, 200),
style);
m_root = m_dvc->AppendContainer(wxDataViewItem(), "The root");
m_child1 = m_dvc->AppendContainer(m_root, "child1");
m_grandchild = m_dvc->AppendItem(m_child1, "grandchild");
m_child2 = m_dvc->AppendItem(m_root, "child2");
m_dvc->Layout();
m_dvc->Expand(m_root);
m_dvc->Refresh();
m_dvc->Update();
}
DataViewCtrlTestCase::~DataViewCtrlTestCase()
{
delete m_dvc;
}
MultiColumnsDataViewCtrlTestCase::MultiColumnsDataViewCtrlTestCase()
: m_size(200, 100),
m_firstColumnWidth(50)
{
m_dvc = new wxDataViewListCtrl(wxTheApp->GetTopWindow(), wxID_ANY);
m_firstColumn =
m_dvc->AppendTextColumn(wxString(), wxDATAVIEW_CELL_INERT, m_firstColumnWidth);
m_lastColumn =
m_dvc->AppendTextColumn(wxString(), wxDATAVIEW_CELL_INERT);
// Set size after columns appending to extend size of the last column.
m_dvc->SetSize(m_size);
m_dvc->Layout();
m_dvc->Refresh();
m_dvc->Update();
}
MultiColumnsDataViewCtrlTestCase::~MultiColumnsDataViewCtrlTestCase()
{
delete m_dvc;
}
DataViewCtrlWithCustomModelTestCase::DataViewCtrlWithCustomModelTestCase()
{
m_dvc = new wxDataViewCtrl(wxTheApp->GetTopWindow(),
wxID_ANY,
wxDefaultPosition,
wxSize(400, 200),
wxDV_SINGLE);
m_model = new DataViewCtrlTestModel();
m_dvc->AssociateModel(m_model);
m_model->DecRef();
m_dvc->AppendColumn(
new wxDataViewColumn(
"Value",
new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT),
0,
m_dvc->FromDIP(200),
wxALIGN_LEFT,
wxDATAVIEW_COL_RESIZABLE));
m_root = m_model->GetDataViewItem(DataViewCtrlTestModel::wxTEST_ITEM_ROOT);
m_child = m_model->GetDataViewItem(DataViewCtrlTestModel::wxTEST_ITEM_CHILD);
m_grandchild =
m_model->GetDataViewItem(DataViewCtrlTestModel::wxTEST_ITEM_GRANDCHILD);
m_grandchildHidden =
m_model->GetDataViewItem(DataViewCtrlTestModel::wxTEST_ITEM_GRANDCHILD_HIDDEN);
m_dvc->Layout();
m_dvc->Expand(m_root);
m_dvc->Refresh();
m_dvc->Update();
}
DataViewCtrlWithCustomModelTestCase::~DataViewCtrlWithCustomModelTestCase()
{
delete m_dvc;
}
// ----------------------------------------------------------------------------
// the tests themselves
// ----------------------------------------------------------------------------
TEST_CASE_METHOD(MultiSelectDataViewCtrlTestCase,
"wxDVC::Selection",
"[wxDataViewCtrl][select]")
{
// Check selection round-trip.
wxDataViewItemArray sel;
sel.push_back(m_child1);
sel.push_back(m_grandchild);
REQUIRE_NOTHROW( m_dvc->SetSelections(sel) );
wxDataViewItemArray sel2;
CHECK( m_dvc->GetSelections(sel2) == static_cast<int>(sel.size()) );
CHECK( sel2 == sel );
// Invalid items in GetSelections() input are supposed to be just skipped.
sel.clear();
sel.push_back(wxDataViewItem());
REQUIRE_NOTHROW( m_dvc->SetSelections(sel) );
CHECK( m_dvc->GetSelections(sel2) == 0 );
CHECK( sel2.empty() );
}
TEST_CASE_METHOD(MultiSelectDataViewCtrlTestCase,
"wxDVC::DeleteSelected",
"[wxDataViewCtrl][delete]")
{
#ifdef __WXGTK__
wxString useASAN;
if ( wxGetEnv("wxUSE_ASAN", &useASAN) && useASAN == "1" )
{
WARN("Skipping test resulting in a memory leak report with wxGTK");
return;
}
#endif // __WXGTK__
wxDataViewItemArray sel;
sel.push_back(m_child1);
sel.push_back(m_grandchild);
sel.push_back(m_child2);
m_dvc->SetSelections(sel);
// delete a selected item
m_dvc->DeleteItem(m_child1);
m_dvc->GetSelections(sel);
// m_child1 and its children should be removed from the selection now
REQUIRE( sel.size() == 1 );
CHECK( sel[0] == m_child2 );
}
TEST_CASE_METHOD(MultiSelectDataViewCtrlTestCase,
"wxDVC::DeleteNotSelected",
"[wxDataViewCtrl][delete]")
{
// TODO not working on OS X as expected
#ifdef __WXOSX__
WARN("Disabled under MacOS because this test currently fails");
#else
wxDataViewItemArray sel;
sel.push_back(m_child1);
sel.push_back(m_grandchild);
m_dvc->SetSelections(sel);
// delete unselected item
m_dvc->DeleteItem(m_child2);
m_dvc->GetSelections(sel);
// m_child1 and its children should be unaffected
REQUIRE( sel.size() == 2 );
CHECK( sel[0] == m_child1 );
CHECK( sel[1] == m_grandchild );
#endif
}
void DataViewCtrlTestCase::TestSelectionFor0and1()
{
wxDataViewItemArray selections;
// Initially there is no selection.
CHECK( m_dvc->GetSelectedItemsCount() == 0 );
CHECK( !m_dvc->HasSelection() );
CHECK( !m_dvc->GetSelection().IsOk() );
CHECK( !m_dvc->GetSelections(selections) );
CHECK( selections.empty() );
// Select one item.
m_dvc->Select(m_child1);
CHECK( m_dvc->GetSelectedItemsCount() == 1 );
CHECK( m_dvc->HasSelection() );
CHECK( m_dvc->GetSelection().IsOk() );
REQUIRE( m_dvc->GetSelections(selections) == 1 );
CHECK( selections[0] == m_child1 );
}
TEST_CASE_METHOD(MultiSelectDataViewCtrlTestCase,
"wxDVC::GetSelectionForMulti",
"[wxDataViewCtrl][select]")
{
wxDataViewItemArray selections;
TestSelectionFor0and1();
m_dvc->Select(m_child2);
CHECK( m_dvc->GetSelectedItemsCount() == 2 );
CHECK( m_dvc->HasSelection() );
CHECK( !m_dvc->GetSelection().IsOk() );
REQUIRE( m_dvc->GetSelections(selections) == 2 );
CHECK( selections[1] == m_child2 );
}
TEST_CASE_METHOD(SingleSelectDataViewCtrlTestCase,
"wxDVC::SingleSelection",
"[wxDataViewCtrl][selection]")
{
TestSelectionFor0and1();
}
TEST_CASE_METHOD(SingleSelectDataViewCtrlTestCase,
"wxDVC::IsExpanded",
"[wxDataViewCtrl][expand]")
{
CHECK( m_dvc->IsExpanded(m_root) );
CHECK( !m_dvc->IsExpanded(m_child1) );
// No idea why, but the native NSOutlineView isItemExpanded: method returns
// true for this item for some reason.
#ifdef __WXOSX__
WARN("Disabled under MacOS: IsExpanded() returns true for grand child");
#else
CHECK( !m_dvc->IsExpanded(m_grandchild) );
#endif
CHECK( !m_dvc->IsExpanded(m_child2) );
m_dvc->Collapse(m_root);
CHECK( !m_dvc->IsExpanded(m_root) );
m_dvc->ExpandChildren(m_root);
CHECK( m_dvc->IsExpanded(m_root) );
CHECK( m_dvc->IsExpanded(m_child1) );
// Expanding an already expanded node must still expand all its children.
m_dvc->Collapse(m_child1);
CHECK( !m_dvc->IsExpanded(m_child1) );
m_dvc->ExpandChildren(m_root);
CHECK( m_dvc->IsExpanded(m_child1) );
}
TEST_CASE_METHOD(DataViewCtrlWithCustomModelTestCase,
"wxDVC::Expand",
"[wxDataViewCtrl][expand]")
{
CHECK( m_dvc->IsExpanded(m_root) );
CHECK( !m_dvc->IsExpanded(m_child) );
#ifdef __WXGTK__
// We need to let the native control have some events to lay itself out.
wxYield();
#endif // __WXGTK__
SECTION( "Was Expanded" )
{
CHECK( m_dvc->GetItemRect(m_grandchild).IsEmpty() );
CHECK( m_dvc->GetItemRect(m_grandchildHidden).IsEmpty() );
m_dvc->Expand(m_child);
#ifdef __WXGTK__
wxYield();
#endif // __WXGTK__
CHECK( !m_dvc->GetItemRect(m_grandchild).IsEmpty() );
CHECK( m_dvc->GetItemRect(m_grandchildHidden).IsEmpty() );
m_dvc->Collapse(m_child);
}
SECTION( "Was Not Expanded" )
{
// Do nothing.
}
// Check wxDataViewModel::ItemAdded().
m_model->ShowSecondGrandChild();
m_dvc->Expand(m_child);
m_dvc->Refresh();
m_dvc->Update();
CHECK( m_dvc->IsExpanded(m_child) );
#ifdef __WXGTK__
// Unfortunately it's not enough to call wxYield() once, so wait up to
// 0.5 sec.
wxStopWatch sw;
while ( m_dvc->GetItemRect(m_grandchild).IsEmpty() )
{
if ( sw.Time() > 500 )
{
WARN("Timed out waiting for wxDataViewCtrl");
break;
}
wxYield();
}
#endif // __WXGTK__
CHECK( !m_dvc->GetItemRect(m_grandchild).IsEmpty() );
CHECK( !m_dvc->GetItemRect(m_grandchildHidden).IsEmpty() );
}
TEST_CASE_METHOD(SingleSelectDataViewCtrlTestCase,
"wxDVC::GetItemRect",
"[wxDataViewCtrl][item]")
{
#ifdef __WXGTK__
// We need to let the native control have some events to lay itself out.
wxYield();
#endif // __WXGTK__
const wxRect rect1 = m_dvc->GetItemRect(m_child1);
const wxRect rect2 = m_dvc->GetItemRect(m_child2);
CHECK( rect1 != wxRect() );
CHECK( rect2 != wxRect() );
CHECK( rect1.x == rect2.x );
CHECK( rect1.width == rect2.width );
CHECK( rect1.height == rect2.height );
{
INFO("First child: " << rect1 << ", second one: " << rect2);
CHECK( rect1.y < rect2.y );
}
// This forces generic implementation to add m_grandchild to the tree, as
// it does it only on demand. We want the item to really be there to check
// that GetItemRect() returns an empty rectangle for collapsed items.
m_dvc->Expand(m_child1);
m_dvc->Collapse(m_child1);
const wxRect rectNotShown = m_dvc->GetItemRect(m_grandchild);
CHECK( rectNotShown == wxRect() );
// Append enough items to make the window scrollable.
for ( int i = 3; i < 100; ++i )
m_dvc->AppendItem(m_root, wxString::Format("child%d", i));
const wxDataViewItem last = m_dvc->AppendItem(m_root, "last");
// This should scroll the window to bring this item into view.
m_dvc->EnsureVisible(last);
#ifdef __WXGTK__
// Wait for the list control to be relaid out.
wxStopWatch sw;
while ( m_dvc->GetTopItem() == m_root )
{
if ( sw.Time() > 500 )
{
WARN("Timed out waiting for wxDataViewCtrl layout");
break;
}
wxYield();
}
#endif // __WXGTK__
// Check that this was indeed the case.
const wxDataViewItem top = m_dvc->GetTopItem();
CHECK( top != m_root );
// Verify that the coordinates are returned in physical coordinates of the
// window and not the logical coordinates affected by scrolling.
const wxRect rectScrolled = m_dvc->GetItemRect(top);
CHECK( rectScrolled.GetBottom() > 0 );
CHECK( rectScrolled.GetTop() <= m_dvc->GetClientSize().y );
// Also check that the root item is not currently visible (because it's
// scrolled off).
const wxRect rectRoot = m_dvc->GetItemRect(m_root);
CHECK( rectRoot == wxRect() );
}
TEST_CASE_METHOD(SingleSelectDataViewCtrlTestCase,
"wxDVC::DeleteAllItems",
"[wxDataViewCtrl][delete]")
{
// The invalid item corresponds to the root of tree store model, so it
// should have a single item (our m_root) initially.
CHECK( m_dvc->GetChildCount(wxDataViewItem()) == 1 );
m_dvc->DeleteAllItems();
// And none at all after deleting all the items.
CHECK( m_dvc->GetChildCount(wxDataViewItem()) == 0 );
}
TEST_CASE_METHOD(MultiColumnsDataViewCtrlTestCase,
"wxDVC::AppendTextColumn",
"[wxDataViewCtrl][column]")
{
#ifdef __WXGTK__
// Wait for the list control to be realized.
wxStopWatch sw;
while ( m_firstColumn->GetWidth() == 0 )
{
if ( sw.Time() > 500 )
{
WARN("Timed out waiting for wxDataViewListCtrl to be realized");
break;
}
wxYield();
}
#endif
// Check the width of the first column.
CHECK( m_firstColumn->GetWidth() == m_firstColumnWidth );
// Check that the last column was extended to fit client area.
const int lastColumnMaxWidth =
m_dvc->GetClientSize().GetWidth() - m_firstColumnWidth;
// In GTK and under Mac the width of the last column is less then
// a remaining client area.
const int lastColumnMinWidth = lastColumnMaxWidth - 10;
CHECK( m_lastColumn->GetWidth() <= lastColumnMaxWidth );
CHECK( m_lastColumn->GetWidth() >= lastColumnMinWidth );
}
#if wxUSE_UIACTIONSIMULATOR
TEST_CASE_METHOD(SingleSelectDataViewCtrlTestCase,
"wxDVC::KeyEvents",
"[wxDataViewCtrl][event]")
{
if ( !EnableUITests() )
return;
EventCounter keyEvents(m_dvc, wxEVT_KEY_DOWN);
m_dvc->SetFocus();
wxUIActionSimulator sim;
sim.Char(WXK_DOWN);
wxYield();
CHECK( keyEvents.GetCount() == 1 );
}
#endif // wxUSE_UIACTIONSIMULATOR
#endif //wxUSE_DATAVIEWCTRL