From e69ac228ea2cd7d30bbda695c817cd3adf18f6c0 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 16 Mar 2025 20:05:48 +0100 Subject: [PATCH] Add unit test for wxAuiNotebook (de)serialization Verify that the serialized data has the expected form and that deserializing it results in the expected results. Also check that invalid data is handled gracefully. --- tests/controls/auitest.cpp | 178 +++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/tests/controls/auitest.cpp b/tests/controls/auitest.cpp index 74c75f6825..9afe20c51b 100644 --- a/tests/controls/auitest.cpp +++ b/tests/controls/auitest.cpp @@ -23,6 +23,7 @@ #include "wx/aui/auibar.h" #include "wx/aui/auibook.h" +#include "wx/aui/serializer.h" #include "asserthelper.h" @@ -168,6 +169,183 @@ TEST_CASE_METHOD(AuiNotebookTestCase, "wxAuiNotebook::FindPage", "[aui]") CHECK( nb->FindPage(p3) == wxNOT_FOUND ); } +TEST_CASE_METHOD(AuiNotebookTestCase, "wxAuiNotebook::Layout", "[aui]") +{ + const auto addPage = [this](int n) + { + return nb->AddPage(new wxPanel(nb), wxString::Format("Page %d", n + 1)); + }; + + for ( int n = 0; n < 5; n++ ) + { + REQUIRE( addPage(n) ); + } + + using Ints = std::vector; + using Indices = std::vector; + + // This serializer allows the code below to tweak its data before using it + // as deserializer. + class TestSerializer : public wxAuiBookSerializer, + public wxAuiBookDeserializer + { + public: + virtual void BeforeSaveNotebook(const wxString& name) override + { + m_name = name; + m_afterSaveCalled = false; + m_tabsLayoutInfo.clear(); + } + + virtual void + SaveNotebookTabControl(const wxAuiTabLayoutInfo& tab) override + { + m_tabsLayoutInfo.push_back(tab); + } + + virtual void AfterSaveNotebook() override + { + m_afterSaveCalled = true; + } + + virtual std::vector + LoadNotebookTabs(const wxString& name) override + { + CHECK( name == m_name ); + + m_orphanedPages.clear(); + + return m_tabsLayoutInfo; + } + + virtual bool + HandleOrphanedPage(wxAuiNotebook& WXUNUSED(book), + int page, + wxAuiTabCtrl** WXUNUSED(tabCtrl), + int* tabIndex) override + { + m_orphanedPages.push_back(page); + + *tabIndex = m_orphanedPageReturnIndex; + + return m_orphanedPageReturnValue; + } + + wxString m_name; + std::vector m_tabsLayoutInfo; + bool m_afterSaveCalled = false; + + Ints m_orphanedPages; + bool m_orphanedPageReturnValue = true; + int m_orphanedPageReturnIndex = wxNOT_FOUND; + } ser; + + // Just for convenience. + auto& info = ser.m_tabsLayoutInfo; + + // Check the default layout has expected representation. + nb->SaveLayout("layout", ser); + CHECK( ser.m_name == "layout" ); + CHECK( ser.m_afterSaveCalled ); + REQUIRE( info.size() == 1 ); + + CHECK( info[0].pages == Ints{} ); + CHECK( info[0].pinned == Ints{} ); + CHECK( info[0].active == 0 ); + + + // Check that the active page is restored correctly. + info[0].active = 1; + nb->LoadLayout("layout", ser); + + CHECK( nb->GetSelection() == 1 ); + + + // Check that page order is serialized as expected. + auto* mainTabCtrl = nb->GetMainTabCtrl(); + REQUIRE( mainTabCtrl ); + CHECK( mainTabCtrl->MovePage(1, 4) ); + + nb->SaveLayout("layout", ser); + REQUIRE( info.size() == 1 ); + CHECK( info[0].pages == Ints{0, 2, 3, 4, 1} ); + + + // Check that pinned pages are serialized as expected. + REQUIRE( nb->SetPageKind(2, wxAuiTabKind::Pinned) ); + + nb->SaveLayout("layout", ser); + REQUIRE( info.size() == 1 ); + + // Note that pinning a page moves it in front of all other pages. + CHECK( info[0].pages == Ints{2, 0, 3, 4, 1} ); + CHECK( info[0].pinned == Ints{2} ); + + + // Check a more complicated case with both locked and pinned pages. + REQUIRE( nb->SetPageKind(4, wxAuiTabKind::Locked) ); + REQUIRE( nb->SetPageKind(3, wxAuiTabKind::Pinned) ); + + nb->SaveLayout("layout", ser); + REQUIRE( info.size() == 1 ); + + // Note that pinning a page moves it in front of all other pages. + CHECK( info[0].pages == Ints{4, 2, 3, 0, 1} ); + CHECK( info[0].pinned == Ints{2, 3} ); + + + // Check that restoring existing layout after adding some pages works. + addPage(5); + addPage(6); + nb->LoadLayout("layout", ser); + CHECK( ser.m_orphanedPages == Ints{5, 6} ); + + // By default, orphaned pages should have been appended. + CHECK( nb->GetPagesInDisplayOrder(mainTabCtrl) == + Indices{4, 2, 3, 0, 1, 5, 6} ); + + // But we can change this by telling deserializer to insert them in front. + ser.m_orphanedPageReturnIndex = 0; + + nb->LoadLayout("layout", ser); + CHECK( ser.m_orphanedPages == Ints{5, 6} ); + + CHECK( nb->GetPagesInDisplayOrder(mainTabCtrl) == + Indices{6, 5, 4, 2, 3, 0, 1} ); + + // Or drop them entirely. + ser.m_orphanedPageReturnValue = false; + + nb->LoadLayout("layout", ser); + CHECK( ser.m_orphanedPages == Ints{5, 6} ); + + CHECK( nb->GetPagesInDisplayOrder(mainTabCtrl) == + Indices{4, 2, 3, 0, 1} ); + + + // Finally, check that invalid data is handled gracefully. + info[0].active = 100; + info[0].pages = Ints{10, 0, 1, 2, 3, 4}; + info[0].pinned = Ints{2, 99, 0}; + + nb->LoadLayout("layout", ser); + CHECK( ser.m_orphanedPages == Ints{} ); + + // Locked tab should have remained first. + CHECK( nb->GetPagesInDisplayOrder(mainTabCtrl) == Indices{4, 0, 1, 2, 3} ); + + // And selection should have been set to it because the specified value was + // invalid. + CHECK( nb->GetSelection() == 4 ); + + // And only tabs appearing before the normal ones can be pinned. + CHECK( nb->GetPageKind(0) == wxAuiTabKind::Pinned ); + CHECK( nb->GetPageKind(1) == wxAuiTabKind::Normal ); + CHECK( nb->GetPageKind(2) == wxAuiTabKind::Normal ); + CHECK( nb->GetPageKind(3) == wxAuiTabKind::Normal ); + CHECK( nb->GetPageKind(4) == wxAuiTabKind::Locked ); +} + TEST_CASE("wxAuiToolBar::Items", "[aui][toolbar]") { std::unique_ptr tbar{new wxAuiToolBar(wxTheApp->GetTopWindow())};