>From 1ba823093642849da539a289d48686bed0547ba9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 19 Dec 2014 17:15:14 +0100 Subject: [PATCH 1/3] Update census paste test to correspond to the revised specification. Put test data inside the test itself instead of extracting it from HTML file. Test changing class defaults (and not only the case defaults). Validate the column values in addition to their presence. --- wx_test_paste_census.cpp | 264 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 210 insertions(+), 54 deletions(-) diff --git a/wx_test_paste_census.cpp b/wx_test_paste_census.cpp index 849908a..dfc9cd0 100644 --- a/wx_test_paste_census.cpp +++ b/wx_test_paste_census.cpp @@ -36,48 +36,17 @@ #include #include #include -#include -#include -#include #include #include #include #include -namespace -{ +#include +#include +#include -// Helper function to get the census data to be pasted. -wxString get_census_data() +namespace { - // Get the census example from help. This is an HTML file but we don't have - // an HTML parser and HTML is not valid XML, so it can't be parsed as such. - // Instead we just rely on the very particular format of this file: right - // now it contains the census data between the only occurrences of
-    // and 
tags in it, so locate them and take everything inside. - - wxFFile f(AddDataDir("pasting_to_a_census.html")); - LMI_ASSERT(f.IsOpened()); - - wxString html; - LMI_ASSERT(f.ReadAll(&html)); - - size_t const pos_pre = html.find("
\n");
-    LMI_ASSERT(pos_pre != wxString::npos);
-
-    size_t const pos_pre_end = html.find("
", pos_pre); - LMI_ASSERT(pos_pre_end != wxString::npos); - - size_t const pos_pre_start = pos_pre + strlen("
\n");
-    wxString const text_pre = html.substr
-                                (pos_pre_start
-                                , pos_pre_end - pos_pre_start
-                                );
-
-    // We're not done yet, we need to deal with the HTML entities. Do use HTML
-    // parsing code in wxWidgets for this at least.
-    return wxHtmlEntitiesParser().Parse(text_pre);
-}
 
 // Helper function to find the wxDataViewCtrl used for the census display.
 //
@@ -119,34 +88,89 @@ wxDataViewListModel* get_census_list_model(wxDataViewCtrl* dvc)
     return list_model;
 }
 
-// Check for the presence of the column with the given name.
-bool does_list_have_column(wxDataViewCtrl* dvc, wxString const& name)
+// Helper for building the diagnostic message in check_list_columns().
+std::string build_not_found_message(std::set const& remaining)
+{
+    std::ostringstream message;
+    bool const only_one = remaining.size() == 1;
+    message << (only_one ? "column" : "columns");
+
+    typedef std::set::const_iterator ssci;
+    for(ssci i = remaining.begin(); i != remaining.end(); ++i)
+        {
+        if(i != remaining.begin())
+            {
+            message << ",";
+            }
+
+        message << " '" << *i << "'";
+        }
+
+    message << (only_one ? "was" : "were") << " not found";
+
+    return message.str();
+}
+
+// Check for the presence of all columns with the given name and, if specified,
+// for the absence of the given one.
+//
+// The 'when' parameter is used solely for the diagnostic messages in case of
+// the check failure.
+void check_list_columns(wxDataViewCtrl* dvc
+                       ,char const* when
+                       ,std::set const& expected
+                       ,std::string const& unexpected = std::string()
+                       )
 {
+    std::set remaining(expected.begin(), expected.end());
+
     unsigned int const num_columns = dvc->GetColumnCount();
     for(unsigned int n = 0; n < num_columns; ++n)
         {
-        if (dvc->GetColumn(n)->GetTitle() == name)
+        std::string const title = dvc->GetColumn(n)->GetTitle().ToStdString();
+        LMI_ASSERT_WITH_MSG
+            (title != unexpected
+            ,"column '" << title << "' unexpectedly found " << when
+            );
+
+        // Notice that it is not an error if the column is not in the expected
+        // columns set, it is not exhaustive.
+        remaining.erase(title);
+        }
+
+    LMI_ASSERT_WITH_MSG
+        (remaining.empty()
+        ,build_not_found_message(remaining) << when
+        );
+}
+
+// Find the index of the column with the given title.
+//
+// Throws an exception if the column is not found.
+unsigned int find_model_column_by_title(wxDataViewCtrl* dvc
+                                       ,std::string const& title
+                                       )
+{
+    unsigned int const num_columns = dvc->GetColumnCount();
+    for(unsigned int n = 0; n < num_columns; ++n)
+        {
+        wxDataViewColumn const* column = dvc->GetColumn(n);
+        if(column->GetTitle().ToStdString() == title)
             {
-            return true;
+            return column->GetModelColumn();
             }
         }
-    return false;
+
+    throw std::runtime_error("column " + title + " not found");
 }
 
 } // Unnamed namespace.
 
-// ERASE THIS BLOCK COMMENT WHEN IMPLEMENTATION COMPLETE. The block
-// comment below changes the original specification, and does not
-// yet describe the present code. Desired changes:
-//  - Save pastable data inline; don't extract from user manual.
-//  - Validate all columns after each step (after initial pasting).
-//  - Test change in class defaults (in addition to case defaults).
-
 /// Test pasting spreadsheet data into a census.
 ///
 /// Create a set of data that might reasonably be copied from a
 /// spreadsheet. Initially at least, use the data in the user manual:
-///   file:///C:/lmi/src/web/lmi/pasting_to_a_census.html
+///   http://www.nongnu.org/lmi/pasting_to_a_census.html
 /// Hardcode the data here; don't read them from the user manual.
 /// (That didactic example was designed mainly to fit on a web page
 /// and to make sense to end users. Some day we might want to make
@@ -181,8 +205,38 @@ bool does_list_have_column(wxDataViewCtrl* dvc, wxString const& name)
 
 LMI_WX_TEST_CASE(paste_census)
 {
+    // The column titles are the user-visible strings corresponding to the
+    // internal column names actually used in the census data below.
+    std::set column_titles;
+    column_titles.insert("Gender");
+    column_titles.insert("Underwriting Class");
+    column_titles.insert("Issue Age");
+    column_titles.insert("Payment");
+    column_titles.insert("Death Benefit Option");
+
+    char const* const census_data =
+        "Gender\tUnderwritingClass\tIssueAge\tPayment\tDeathBenefitOption\n"
+        "\n"
+        "Female\tPreferred\t30\tsevenpay,7;0\tb,7;a\n"
+        "Male\tPreferred\t35\tsevenpay,7;0\tb,7;a\n"
+        "Female\tStandard\t40\tsevenpay,7;0\tb,7;a\n"
+        "Male\tStandard\t45\tsevenpay,7;0\tb,7;a\n"
+        "Female\tPreferred\t50\tsevenpay,7;0\tb,7;a\n"
+        "Male\tPreferred\t55\tsevenpay,7;0\tb,7;a\n"
+        "Female\tStandard\t60\tsevenpay,7;0\tb,7;a\n"
+        ;
+
+    std::size_t const number_of_rows = std::count
+                                        (census_data
+                                        ,census_data + std::strlen(census_data)
+                                        ,'\n'
+                                        )
+                                        - 1 // Not counting the header.
+                                        - 1 // Nor the empty line after it.
+                                        ;
+
     // Put the data to paste on clipboard.
-    ClipboardEx::SetText(get_census_data().ToStdString());
+    ClipboardEx::SetText(census_data);
 
     // Create a new census.
     wx_test_new_census census;
@@ -196,10 +250,105 @@ LMI_WX_TEST_CASE(paste_census)
     // correctly.
     wxDataViewCtrl* const list_window = find_census_list_window();
     wxDataViewListModel* const list_model = get_census_list_model(list_window);
-    LMI_ASSERT_EQUAL(list_model->GetCount(), 7);
+    LMI_ASSERT_EQUAL(list_model->GetCount(), number_of_rows);
+
+    check_list_columns
+        (list_window
+        ,"after pasting initial census data"
+        ,column_titles
+        );
+
+    // Change class defaults: this requires a selection, so ensure we have one
+    // by clicking somewhere inside the control.
+    ui.MouseMove
+        (list_window->ClientToScreen
+            (wxPoint
+                (10*list_window->GetCharWidth()
+                ,3*list_window->GetCharHeight()
+                )
+            )
+        );
+    ui.MouseClick();
+    wxYield();
+
+    LMI_ASSERT_EQUAL(list_window->GetSelectedItemsCount(), 1);
+
+    ui.Char('e', wxMOD_CONTROL | wxMOD_ALT); // "Census|Edit class defaults"
+
+    struct change_gender_in_class_defaults_dialog
+        :public wxExpectModalBase
+    {
+        virtual int OnInvoked(MvcController* dialog) const
+            {
+            dialog->Show();
+            wxYield();
+
+            wxUIActionSimulator ui;
+
+            // Go to the third page: as the dialog remembers its last opened
+            // page, ensure that we start from the first one.
+            ui.Char(WXK_HOME);
+            ui.Char(WXK_RIGHT);
+            ui.Char(WXK_RIGHT);
+            wxYield();
 
-    static char const* column_title = "Underwriting Class";
-    LMI_ASSERT(does_list_have_column(list_window, column_title));
+            // We can't find directly the radio button we're interested in,
+            // because it's not a real wxWindow, so we need to find the radio
+            // box containing it.
+            wxWindow* const gender_window = wxWindow::FindWindowByName
+                ("Gender"
+                ,dialog
+                );
+            LMI_ASSERT(gender_window);
+
+            wxRadioBox* const
+                gender_radiobox = dynamic_cast(gender_window);
+            LMI_ASSERT(gender_radiobox);
+
+            // It's difficult to select the radiobox using just
+            // wxUIActionSimulator as there is no keyboard shortcut to navigate
+            // to it and emulating a mouse click on it is tricky as we don't
+            // want to change its selection by clicking on the item, so do it
+            // programmatically, the effect should be absolutely the same.
+            gender_radiobox->SetFocus();
+            wxYield();
+
+            ui.Char(WXK_DOWN); // Select the last, "Unisex", radio button.
+            wxYield();
+
+            LMI_ASSERT_EQUAL(gender_radiobox->GetSelection(), 2);
+
+            return wxID_OK;
+            }
+    };
+
+    // The menu command above should have opened the "Class defaults" dialog and
+    // our code dealing with it above is supposed to result in an appearance of
+    // "Apply all changes to every cell?" message box for which we provide an
+    // affirmative answer.
+    wxTEST_DIALOG
+        (wxYield()
+        ,change_gender_in_class_defaults_dialog()
+        ,wxExpectModal(wxYES)
+        );
+
+    // Check that all columns, including the "Gender" one, are still shown.
+    check_list_columns
+        (list_window
+        ,"after changing gender in class defaults"
+        ,column_titles
+        );
+
+    // Verify that the "Gender" column value is "Unisex" in every row now.
+    unsigned int const
+        gender_column = find_model_column_by_title(list_window, "Gender");
+    LMI_ASSERT_EQUAL(list_model->GetCount(), number_of_rows);
+    for(std::size_t row = 0; row < number_of_rows; ++row)
+        {
+        wxVariant value;
+        list_model->GetValueByRow(value, row, gender_column);
+        LMI_ASSERT_EQUAL(value.GetString(), "Unisex");
+        }
 
     // Change the case defaults to get rid of the underwriting class.
     ui.Char('e', wxMOD_CONTROL | wxMOD_SHIFT); // "Census|Edit case defaults"
@@ -263,8 +412,15 @@ LMI_WX_TEST_CASE(paste_census)
 
     // Check that we still have the same cells but that now the underwriting
     // class column has disappeared as its value has been fixed.
-    LMI_ASSERT_EQUAL(list_model->GetCount(), 7);
-    LMI_ASSERT(!does_list_have_column(list_window, column_title));
+    LMI_ASSERT_EQUAL(list_model->GetCount(), number_of_rows);
+
+    column_titles.erase("Underwriting Class");
+    check_list_columns
+        (list_window
+        ,"after changing class in case defaults"
+        ,column_titles
+        ,"Underwriting Class"
+        );
 
     // Finally save the census with the pasted data for later inspection.
     wxString const census_file_name = get_test_file_path_for("PasteCensus.cns");
-- 
2.1.0