///////////////////////////////////////////////////////////////////////////////
-// Name: tests/textctrl/textctrltest.cpp
+// Name: tests/controls/textctrltest.cpp
// Purpose: wxTextCtrl unit test
// Author: Vadim Zeitlin
// Created: 2007-09-25
#include "testprec.h"
+#if wxUSE_TEXTCTRL
+
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#include "wx/textctrl.h"
#endif // WX_PRECOMP
+#include "wx/scopeguard.h"
+
+#include "textentrytest.h"
+#include "testableframe.h"
+#include "asserthelper.h"
+#include "wx/uiaction.h"
+
+static const int TEXT_HEIGHT = 200;
+
// ----------------------------------------------------------------------------
// test class
// ----------------------------------------------------------------------------
-class TextCtrlTestCase : public CppUnit::TestCase
+class TextCtrlTestCase : public TextEntryTestCase, public CppUnit::TestCase
{
public:
TextCtrlTestCase() { }
virtual void tearDown();
private:
+ virtual wxTextEntry *GetTestEntry() const { return m_text; }
+ virtual wxWindow *GetTestWindow() const { return m_text; }
+
+ #define SINGLE_AND_MULTI_TESTS() \
+ WXUISIM_TEST( ReadOnly ); \
+ CPPUNIT_TEST( StreamInput ); \
+ CPPUNIT_TEST( Redirector )
+
CPPUNIT_TEST_SUITE( TextCtrlTestCase );
+ // These tests run for single line text controls.
+ wxTEXT_ENTRY_TESTS();
+ WXUISIM_TEST( MaxLength );
+ SINGLE_AND_MULTI_TESTS();
+
+ // Now switch to the multi-line text controls.
+ CPPUNIT_TEST( PseudoTestSwitchToMultiLineStyle );
+
+ // Rerun some of the tests above. Notice that not all of them pass, so
+ // we can't just use wxTEXT_ENTRY_TESTS() here. For some of them it's
+ // normal, e.g. Hint() test isn't supposed to work for multi-line
+ // controls. Others, such as InsertionPoint() and TextChangeEvents()
+ // don't pass neither but this could be a bug.
CPPUNIT_TEST( SetValue );
- CPPUNIT_TEST( TextChangeEvents );
+ CPPUNIT_TEST( Selection );
+ CPPUNIT_TEST( Replace );
+ WXUISIM_TEST( Editable );
+ CPPUNIT_TEST( CopyPaste );
+ CPPUNIT_TEST( UndoRedo );
+
+ SINGLE_AND_MULTI_TESTS();
+
+
+ // All tests from now on are for multi-line controls only.
+ CPPUNIT_TEST( MultiLineReplace );
+ //WXUISIM_TEST( ProcessEnter );
+ WXUISIM_TEST( Url );
+ CPPUNIT_TEST( Style );
+ CPPUNIT_TEST( FontStyle );
+ CPPUNIT_TEST( Lines );
+ CPPUNIT_TEST( LogTextCtrl );
+ CPPUNIT_TEST( PositionToCoords );
+ CPPUNIT_TEST( PositionToCoordsRich );
+ CPPUNIT_TEST( PositionToCoordsRich2 );
CPPUNIT_TEST_SUITE_END();
- void SetValue();
- void TextChangeEvents();
+ void PseudoTestSwitchToMultiLineStyle()
+ {
+ ms_style = wxTE_MULTILINE;
+ }
+
+ void MultiLineReplace();
+ void ReadOnly();
+ void MaxLength();
+ void StreamInput();
+ void Redirector();
+ //void ProcessEnter();
+ void Url();
+ void Style();
+ void FontStyle();
+ void Lines();
+ void LogTextCtrl();
+ void PositionToCoords();
+ void PositionToCoordsRich();
+ void PositionToCoordsRich2();
+
+ void DoPositionToCoordsTestWithStyle(long style);
+
+ // Create the control with the following styles added to ms_style which may
+ // (or not) already contain wxTE_MULTILINE.
+ void CreateText(long extraStyles);
wxTextCtrl *m_text;
- wxFrame *m_frame;
+
+ static long ms_style;
DECLARE_NO_COPY_CLASS(TextCtrlTestCase)
};
// register in the unnamed registry so that these tests are run by default
CPPUNIT_TEST_SUITE_REGISTRATION( TextCtrlTestCase );
-// also include in it's own registry so that these tests can be run alone
+// also include in its own registry so that these tests can be run alone
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( TextCtrlTestCase, "TextCtrlTestCase" );
// ----------------------------------------------------------------------------
// test initialization
// ----------------------------------------------------------------------------
+// This is 0 initially and set to wxTE_MULTILINE later to allow running the
+// same tests for both single and multi line controls.
+long TextCtrlTestCase::ms_style = 0;
+
+void TextCtrlTestCase::CreateText(long extraStyles)
+{
+ wxSize size;
+ if ( ms_style == wxTE_MULTILINE )
+ size = wxSize(400, TEXT_HEIGHT);
+
+ m_text = new wxTextCtrl(wxTheApp->GetTopWindow(), wxID_ANY, "",
+ wxDefaultPosition, size,
+ ms_style | extraStyles);
+}
+
void TextCtrlTestCase::setUp()
{
- m_text = new wxTextCtrl(wxTheApp->GetTopWindow(), wxID_ANY);
+ CreateText(ms_style);
}
void TextCtrlTestCase::tearDown()
{
- delete m_text;
- m_text = NULL;
+ wxDELETE(m_text);
}
// ----------------------------------------------------------------------------
// tests themselves
// ----------------------------------------------------------------------------
-void TextCtrlTestCase::SetValue()
+void TextCtrlTestCase::MultiLineReplace()
+{
+ m_text->SetValue("Hello replace\n"
+ "0123456789012");
+ m_text->SetInsertionPoint(0);
+
+ m_text->Replace(6, 13, "changed");
+
+ CPPUNIT_ASSERT_EQUAL("Hello changed\n"
+ "0123456789012",
+ m_text->GetValue());
+ CPPUNIT_ASSERT_EQUAL(13, m_text->GetInsertionPoint());
+
+ m_text->Replace(13, -1, "");
+ CPPUNIT_ASSERT_EQUAL("Hello changed", m_text->GetValue());
+ CPPUNIT_ASSERT_EQUAL(13, m_text->GetInsertionPoint());
+}
+
+void TextCtrlTestCase::ReadOnly()
{
- CPPUNIT_ASSERT( m_text->IsEmpty() );
+#if wxUSE_UIACTIONSIMULATOR
+ // we need a read only control for this test so recreate it
+ delete m_text;
+ CreateText(wxTE_READONLY);
+
+ EventCounter updated(m_text, wxEVT_TEXT);
+
+ m_text->SetFocus();
+
+ wxUIActionSimulator sim;
+ sim.Text("abcdef");
+ wxYield();
+
+ CPPUNIT_ASSERT_EQUAL("", m_text->GetValue());
+ CPPUNIT_ASSERT_EQUAL(0, updated.GetCount());
+
+ // SetEditable() is supposed to override wxTE_READONLY
+ m_text->SetEditable(true);
+
+#ifdef __WXOSX__
+ // a ready only text field might not have been focusable at all
+ m_text->SetFocus();
+#endif
+
+ sim.Text("abcdef");
+ wxYield();
+
+ CPPUNIT_ASSERT_EQUAL("abcdef", m_text->GetValue());
+ CPPUNIT_ASSERT_EQUAL(6, updated.GetCount());
+#endif
+}
+
+void TextCtrlTestCase::MaxLength()
+{
+#if wxUSE_UIACTIONSIMULATOR
+ EventCounter updated(m_text, wxEVT_TEXT);
+ EventCounter maxlen(m_text, wxEVT_TEXT_MAXLEN);
+
+ m_text->SetFocus();
+ m_text->SetMaxLength(10);
+
+ wxUIActionSimulator sim;
+ sim.Text("abcdef");
+ wxYield();
+
+ CPPUNIT_ASSERT_EQUAL(0, maxlen.GetCount());
+
+ sim.Text("ghij");
+ wxYield();
+
+ CPPUNIT_ASSERT_EQUAL(0, maxlen.GetCount());
+ CPPUNIT_ASSERT_EQUAL(10, updated.GetCount());
- m_text->SetValue("foo");
- WX_ASSERT_STR_EQUAL( "foo", m_text->GetValue() );
+ maxlen.Clear();
+ updated.Clear();
+
+ sim.Text("k");
+ wxYield();
+
+ CPPUNIT_ASSERT_EQUAL(1, maxlen.GetCount());
+ CPPUNIT_ASSERT_EQUAL(0, updated.GetCount());
+
+ maxlen.Clear();
+ updated.Clear();
+
+ m_text->SetMaxLength(0);
+
+ sim.Text("k");
+ wxYield();
+
+ CPPUNIT_ASSERT_EQUAL(0, maxlen.GetCount());
+ CPPUNIT_ASSERT_EQUAL(1, updated.GetCount());
+#endif
+}
+
+void TextCtrlTestCase::StreamInput()
+{
+#ifndef __WXOSX__
+ {
+ // Ensure we use decimal point and not a comma.
+ char * const locOld = setlocale(LC_NUMERIC, "C");
+ wxON_BLOCK_EXIT2( setlocale, (int)LC_NUMERIC, locOld );
+
+ *m_text << "stringinput"
+ << 10
+ << 1000L
+ << 3.14f
+ << 2.71
+ << 'a'
+ << L'b';
+ }
+
+ CPPUNIT_ASSERT_EQUAL("stringinput1010003.142.71ab", m_text->GetValue());
m_text->SetValue("");
- CPPUNIT_ASSERT( m_text->IsEmpty() );
- m_text->SetValue("hi");
- WX_ASSERT_STR_EQUAL( "hi", m_text->GetValue() );
+#if wxHAS_TEXT_WINDOW_STREAM
+
+ std::ostream stream(m_text);
+
+ // We don't test a wide character as this is not a wide stream
+ stream << "stringinput"
+ << 10
+ << 1000L
+ << 3.14f
+ << 2.71
+ << 'a';
+
+ stream.flush();
+
+ CPPUNIT_ASSERT_EQUAL("stringinput1010003.142.71a", m_text->GetValue());
+
+#endif // wxHAS_TEXT_WINDOW_STREAM
+#endif // !__WXOSX__
+}
+
+void TextCtrlTestCase::Redirector()
+{
+#if wxHAS_TEXT_WINDOW_STREAM && wxUSE_STD_IOSTREAM
+
+ wxStreamToTextRedirector redirect(m_text);
+
+ std::cout << "stringinput"
+ << 10
+ << 1000L
+ << 3.14f
+ << 2.71
+ << 'a';
+
+ CPPUNIT_ASSERT_EQUAL("stringinput1010003.142.71a", m_text->GetValue());
+
+#endif
+}
+
+#if 0
+void TextCtrlTestCase::ProcessEnter()
+{
+#if wxUSE_UIACTIONSIMULATOR
+ wxTestableFrame* frame = wxStaticCast(wxTheApp->GetTopWindow(),
+ wxTestableFrame);
+
+ EventCounter count(m_text, wxEVT_TEXT_ENTER);
+
+ m_text->SetFocus();
+
+ wxUIActionSimulator sim;
+ sim.Char(WXK_RETURN);
+ wxYield();
+
+ CPPUNIT_ASSERT_EQUAL(0, frame->GetEventCount(wxEVT_TEXT_ENTER));
+
+ // we need a text control with wxTE_PROCESS_ENTER for this test
+ delete m_text;
+ CreateText(wxTE_PROCESS_ENTER);
+
+ m_text->SetFocus();
+
+ sim.Char(WXK_RETURN);
+ wxYield();
- m_text->SetValue("bye");
- WX_ASSERT_STR_EQUAL( "bye", m_text->GetValue() );
+ CPPUNIT_ASSERT_EQUAL(1, frame->GetEventCount(wxEVT_TEXT_ENTER));
+#endif
}
+#endif
-void TextCtrlTestCase::TextChangeEvents()
+void TextCtrlTestCase::Url()
{
- class TextTestEventHandler : public wxEvtHandler
+#if wxUSE_UIACTIONSIMULATOR && defined(__WXMSW__)
+ delete m_text;
+ CreateText(wxTE_RICH | wxTE_AUTO_URL);
+
+ EventCounter url(m_text, wxEVT_TEXT_URL);
+
+ m_text->AppendText("http://www.wxwidgets.org");
+
+ wxUIActionSimulator sim;
+ sim.MouseMove(m_text->ClientToScreen(wxPoint(5, 5)));
+ sim.MouseClick();
+ wxYield();
+
+ CPPUNIT_ASSERT_EQUAL(1, url.GetCount());
+#endif
+}
+
+void TextCtrlTestCase::Style()
+{
+#ifndef __WXOSX__
+ delete m_text;
+ // We need wxTE_RICH under windows for style support
+ CreateText(wxTE_RICH);
+
+ // Red text on a white background
+ m_text->SetDefaultStyle(wxTextAttr(*wxRED, *wxWHITE));
+
+ CPPUNIT_ASSERT_EQUAL(m_text->GetDefaultStyle().GetTextColour(), *wxRED);
+ CPPUNIT_ASSERT_EQUAL(m_text->GetDefaultStyle().GetBackgroundColour(),
+ *wxWHITE);
+
+ m_text->AppendText("red on white ");
+
+ // Red text on a grey background
+ m_text->SetDefaultStyle(wxTextAttr(wxNullColour, *wxLIGHT_GREY));
+
+ CPPUNIT_ASSERT_EQUAL(m_text->GetDefaultStyle().GetTextColour(), *wxRED);
+ CPPUNIT_ASSERT_EQUAL(m_text->GetDefaultStyle().GetBackgroundColour(),
+ *wxLIGHT_GREY);
+
+ m_text->AppendText("red on grey ");
+
+ // Blue text on a grey background
+ m_text->SetDefaultStyle(wxTextAttr(*wxBLUE));
+
+
+ CPPUNIT_ASSERT_EQUAL(m_text->GetDefaultStyle().GetTextColour(), *wxBLUE);
+ CPPUNIT_ASSERT_EQUAL(m_text->GetDefaultStyle().GetBackgroundColour(),
+ *wxLIGHT_GREY);
+
+ m_text->AppendText("blue on grey");
+
+ // Get getting the style at a specific location
+ wxTextAttr style;
+
+ // We have to check that styles are supported
+ if(m_text->GetStyle(3, style))
{
- public:
- TextTestEventHandler() { m_events = 0; }
+ CPPUNIT_ASSERT_EQUAL(style.GetTextColour(), *wxRED);
+ CPPUNIT_ASSERT_EQUAL(style.GetBackgroundColour(), *wxWHITE);
+ }
- // calling this automatically resets the events counter
- int GetEvents()
- {
- const int events = m_events;
- m_events = 0;
- return events;
- }
+ // And then setting the style
+ if(m_text->SetStyle(15, 18, style))
+ {
+ m_text->GetStyle(17, style);
- void OnText(wxCommandEvent& WXUNUSED(event)) { m_events++; }
+ CPPUNIT_ASSERT_EQUAL(style.GetTextColour(), *wxRED);
+ CPPUNIT_ASSERT_EQUAL(style.GetBackgroundColour(), *wxWHITE);
+ }
+#endif
+}
- private:
- int m_events;
- } handler;
+void TextCtrlTestCase::FontStyle()
+{
+ // We need wxTE_RICH under MSW and wxTE_MULTILINE under GTK for style
+ // support so recreate the control with these styles.
+ delete m_text;
+ CreateText(wxTE_RICH);
+
+ // Check that we get back the same font from GetStyle() after setting it
+ // with SetDefaultStyle().
+ wxFont fontIn(14,
+ wxFONTFAMILY_DEFAULT,
+ wxFONTSTYLE_NORMAL,
+ wxFONTWEIGHT_NORMAL);
+ wxTextAttr attrIn;
+ attrIn.SetFont(fontIn);
+ if ( !m_text->SetDefaultStyle(attrIn) )
+ {
+ // Skip the test if the styles are not supported.
+ return;
+ }
- m_text->Connect(wxEVT_COMMAND_TEXT_UPDATED,
- wxCommandEventHandler(TextTestEventHandler::OnText),
- NULL,
- &handler);
+ m_text->AppendText("Default font size 14");
- // notice that SetValue() generates an event even if the text didn't change
- m_text->SetValue("");
- CPPUNIT_ASSERT_EQUAL( 1, handler.GetEvents() );
+ wxTextAttr attrOut;
+ m_text->GetStyle(5, attrOut);
- m_text->SetValue("foo");
- CPPUNIT_ASSERT_EQUAL( 1, handler.GetEvents() );
+ CPPUNIT_ASSERT( attrOut.HasFont() );
- m_text->SetValue("foo");
- CPPUNIT_ASSERT_EQUAL( 1, handler.GetEvents() );
+ wxFont fontOut = attrOut.GetFont();
+#ifdef __WXMSW__
+ // Under MSW we get back an encoding in the font even though we hadn't
+ // specified it originally. It's not really a problem but we need this hack
+ // to prevent the assert below from failing because of it.
+ fontOut.SetEncoding(fontIn.GetEncoding());
+#endif
+ CPPUNIT_ASSERT_EQUAL( fontIn, fontOut );
- m_text->ChangeValue("bar");
- CPPUNIT_ASSERT_EQUAL( 0, handler.GetEvents() );
- m_text->AppendText("bar");
- CPPUNIT_ASSERT_EQUAL( 1, handler.GetEvents() );
+ // Also check the same for SetStyle().
+ fontIn.SetPointSize(10);
+ fontIn.SetWeight(wxFONTWEIGHT_BOLD);
+ attrIn.SetFont(fontIn);
+ m_text->SetStyle(0, 6, attrIn);
- m_text->Replace(3, 6, "baz");
- CPPUNIT_ASSERT_EQUAL( 1, handler.GetEvents() );
+ m_text->GetStyle(4, attrOut);
+ CPPUNIT_ASSERT( attrOut.HasFont() );
- m_text->Remove(0, 3);
- CPPUNIT_ASSERT_EQUAL( 1, handler.GetEvents() );
+ fontOut = attrOut.GetFont();
+#ifdef __WXMSW__
+ fontOut.SetEncoding(fontIn.GetEncoding());
+#endif
+ CPPUNIT_ASSERT_EQUAL( fontIn, fontOut );
+}
- m_text->WriteText("foo");
- CPPUNIT_ASSERT_EQUAL( 1, handler.GetEvents() );
+void TextCtrlTestCase::Lines()
+{
+ m_text->SetValue("line1\nline2\nlong long line 3");
+ m_text->Refresh();
+ m_text->Update();
+
+ CPPUNIT_ASSERT_EQUAL(3, m_text->GetNumberOfLines());
+ CPPUNIT_ASSERT_EQUAL(5, m_text->GetLineLength(0));
+ CPPUNIT_ASSERT_EQUAL("line2", m_text->GetLineText(1));
+ CPPUNIT_ASSERT_EQUAL(16, m_text->GetLineLength(2));
+
+ m_text->AppendText("\n\nMore text on line 5");
+
+ CPPUNIT_ASSERT_EQUAL(5, m_text->GetNumberOfLines());
+ CPPUNIT_ASSERT_EQUAL(0, m_text->GetLineLength(3));
+ CPPUNIT_ASSERT_EQUAL("", m_text->GetLineText(3));
+
+ // Verify that wrapped lines count as 2 lines.
+ //
+ // This currently doesn't work neither in wxGTK nor wxOSX/Cocoa, see
+ // #12366, where GetNumberOfLines() always returns the number of logical,
+ // not physical, lines.
+ m_text->AppendText("\n" + wxString(50, '1') + ' ' + wxString(50, '2'));
+#if defined(__WXGTK__) || defined(__WXOSX_COCOA__)
+ CPPUNIT_ASSERT_EQUAL(6, m_text->GetNumberOfLines());
+#else
+ CPPUNIT_ASSERT_EQUAL(7, m_text->GetNumberOfLines());
+#endif
+}
- m_text->Clear();
- CPPUNIT_ASSERT_EQUAL( 1, handler.GetEvents() );
+void TextCtrlTestCase::LogTextCtrl()
+{
+ CPPUNIT_ASSERT(m_text->IsEmpty());
+
+ wxLogTextCtrl* logtext = new wxLogTextCtrl(m_text);
+
+ wxLog* old = wxLog::SetActiveTarget(logtext);
+
+ logtext->LogText("text");
+
+ delete wxLog::SetActiveTarget(old);
+
+ CPPUNIT_ASSERT(!m_text->IsEmpty());
}
+void TextCtrlTestCase::PositionToCoords()
+{
+ DoPositionToCoordsTestWithStyle(0);
+}
+
+void TextCtrlTestCase::PositionToCoordsRich()
+{
+ DoPositionToCoordsTestWithStyle(wxTE_RICH);
+}
+
+void TextCtrlTestCase::PositionToCoordsRich2()
+{
+ DoPositionToCoordsTestWithStyle(wxTE_RICH2);
+}
+
+void TextCtrlTestCase::DoPositionToCoordsTestWithStyle(long style)
+{
+ delete m_text;
+ CreateText(style);
+
+ // Asking for invalid index should fail.
+ WX_ASSERT_FAILS_WITH_ASSERT( m_text->PositionToCoords(1) );
+
+ // Getting position shouldn't return wxDefaultPosition except if the method
+ // is not implemented at all in the current port.
+ const wxPoint pos0 = m_text->PositionToCoords(0);
+ if ( pos0 == wxDefaultPosition )
+ {
+#if defined(__WXMSW__) || defined(__WXGTK20__)
+ CPPUNIT_FAIL( "PositionToCoords() unexpectedly failed." );
+#endif
+ return;
+ }
+
+ CPPUNIT_ASSERT(pos0.x >= 0);
+ CPPUNIT_ASSERT(pos0.y >= 0);
+
+
+ m_text->SetValue("Hello");
+ wxYield(); // Let GTK layout the control correctly.
+
+ // Position of non-first character should be positive.
+ const long posHello4 = m_text->PositionToCoords(4).x;
+ CPPUNIT_ASSERT( posHello4 > 0 );
+
+ // Asking for position beyond the last character should succeed and return
+ // reasonable result.
+ CPPUNIT_ASSERT( m_text->PositionToCoords(5).x > posHello4 );
+
+ // But asking for the next position should fail.
+ WX_ASSERT_FAILS_WITH_ASSERT( m_text->PositionToCoords(6) );
+
+ // Test getting the coordinates of the last character when it is in the
+ // beginning of a new line to exercise MSW code which has specific logic
+ // for it.
+ m_text->AppendText("\n");
+ const wxPoint posLast = m_text->PositionToCoords(m_text->GetLastPosition());
+ CPPUNIT_ASSERT_EQUAL( pos0.x, posLast.x );
+ CPPUNIT_ASSERT( posLast.y > 0 );
+
+
+ // Add enough contents to the control to make sure it has a scrollbar.
+ m_text->SetValue("First line" + wxString(50, '\n') + "Last line");
+ m_text->SetInsertionPoint(0);
+ wxYield(); // Let GTK layout the control correctly.
+
+ // This shouldn't change anything for the first position coordinates.
+ CPPUNIT_ASSERT_EQUAL( pos0, m_text->PositionToCoords(0) );
+
+ // And the last one must be beyond the window boundary and so not be
+ // visible -- but getting its coordinate should still work.
+ CPPUNIT_ASSERT
+ (
+ m_text->PositionToCoords(m_text->GetLastPosition()).y > TEXT_HEIGHT
+ );
+
+
+ // Now make it scroll to the end and check that the first position now has
+ // negative offset as its above the visible part of the window while the
+ // last position is in its bounds.
+ m_text->SetInsertionPointEnd();
+
+ CPPUNIT_ASSERT( m_text->PositionToCoords(0).y < 0 );
+ CPPUNIT_ASSERT
+ (
+ m_text->PositionToCoords(m_text->GetInsertionPoint()).y <= TEXT_HEIGHT
+ );
+}
+
+
+#endif //wxUSE_TEXTCTRL