wxWidgets/tests/validators/valnum.cpp
Vadim Zeitlin cf93b63ca1 Relax checks on character entry in numeric validators
We can't perform the check for the invalid number in IsCharOk() because
it's perfectly normal for the number to be temporarily invalid while
it's being entered and, worse, sometimes this can't be avoided at all
and the existing behaviour prevented the user from entering _anything_
into a control limited to the values between 10 and 20.

Do ensure that the control still has a correct value after losing focus.

See #12968, #23561.

(combined cherry pick of c269932c4e8ed0c0e4a2554b928ae2b55f70ad8e,
dccddcdd59b9a11564b809c516b418356db21718 and
1c160557132f76423dbc0acb3a01b1f64f016dd1 from master)
2023-07-03 02:43:58 +02:00

370 lines
9.8 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: tests/validators/valnum.cpp
// Purpose: Unit tests for numeric validators.
// Author: Vadim Zeitlin
// Created: 2011-01-18
// Copyright: (c) 2011 Vadim Zeitlin <vadim@wxwidgets.org>
///////////////////////////////////////////////////////////////////////////////
#include "testprec.h"
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/intl.h"
#include "wx/textctrl.h"
#include "wx/validate.h"
#endif // WX_PRECOMP
#include "wx/valnum.h"
#include "asserthelper.h"
#include "testableframe.h"
#include "wx/scopeguard.h"
#include "wx/uiaction.h"
class NumValidatorTestCase
{
public:
NumValidatorTestCase();
~NumValidatorTestCase();
protected:
wxTextCtrl* const m_text;
wxDECLARE_NO_COPY_CLASS(NumValidatorTestCase);
};
NumValidatorTestCase::NumValidatorTestCase()
: m_text(new wxTextCtrl(wxTheApp->GetTopWindow(), wxID_ANY))
{
}
NumValidatorTestCase::~NumValidatorTestCase()
{
delete m_text;
}
TEST_CASE_METHOD(NumValidatorTestCase, "ValNum::TransferInt", "[valnum]")
{
int value = 0;
wxIntegerValidator<int> valInt(&value);
valInt.SetWindow(m_text);
CHECK( valInt.TransferToWindow() );
CHECK( m_text->GetValue() == "0" );
value = 17;
CHECK( valInt.TransferToWindow() );
CHECK( m_text->GetValue() == "17" );
m_text->ChangeValue("foobar");
CHECK( !valInt.TransferFromWindow() );
m_text->ChangeValue("-234");
CHECK( valInt.TransferFromWindow() );
CHECK( value == -234 );
m_text->ChangeValue("9223372036854775808"); // == LLONG_MAX + 1
CHECK( !valInt.TransferFromWindow() );
m_text->Clear();
CHECK( !valInt.TransferFromWindow() );
}
TEST_CASE_METHOD(NumValidatorTestCase, "ValNum::TransferUnsigned", "[valnum]")
{
unsigned value = 0;
wxIntegerValidator<unsigned> valUnsigned(&value);
valUnsigned.SetWindow(m_text);
CHECK( valUnsigned.TransferToWindow() );
CHECK( m_text->GetValue() == "0" );
value = 17;
CHECK( valUnsigned.TransferToWindow() );
CHECK( m_text->GetValue() == "17" );
m_text->ChangeValue("foobar");
CHECK( !valUnsigned.TransferFromWindow() );
m_text->ChangeValue("-234");
CHECK( !valUnsigned.TransferFromWindow() );
m_text->ChangeValue("234");
CHECK( valUnsigned.TransferFromWindow() );
CHECK( value == 234 );
m_text->ChangeValue("4294967295"); // == ULONG_MAX in 32 bits
CHECK( valUnsigned.TransferFromWindow() );
CHECK( value == wxUINT32_MAX );
CHECK( valUnsigned.TransferToWindow() );
CHECK( m_text->GetValue() == "4294967295" );
m_text->ChangeValue("4294967296"); // == ULONG_MAX + 1
CHECK( !valUnsigned.TransferFromWindow() );
m_text->ChangeValue("18446744073709551616"); // == ULLONG_MAX + 1
CHECK( !valUnsigned.TransferFromWindow() );
m_text->Clear();
CHECK( !valUnsigned.TransferFromWindow() );
}
TEST_CASE_METHOD(NumValidatorTestCase, "ValNum::TransferUnsignedRange", "[valnum]")
{
unsigned value = 1;
wxIntegerValidator<unsigned> valUnsigned(&value, 1, 20);
valUnsigned.SetWindow(m_text);
CHECK( valUnsigned.TransferToWindow() );
CHECK( m_text->GetValue() == "1" );
value = 17;
CHECK( valUnsigned.TransferToWindow() );
CHECK( m_text->GetValue() == "17" );
m_text->ChangeValue("foobar");
CHECK( !valUnsigned.TransferFromWindow() );
m_text->ChangeValue("0");
CHECK( !valUnsigned.TransferFromWindow() );
m_text->ChangeValue("1");
CHECK( valUnsigned.TransferFromWindow() );
CHECK( value == 1);
m_text->ChangeValue("20");
CHECK( valUnsigned.TransferFromWindow() );
CHECK( value == 20);
m_text->ChangeValue("21");
CHECK( !valUnsigned.TransferFromWindow() );
m_text->Clear();
CHECK( !valUnsigned.TransferFromWindow() );
}
TEST_CASE_METHOD(NumValidatorTestCase, "ValNum::TransferULL", "[valnum]")
{
unsigned long long value = 0;
wxIntegerValidator<unsigned long long> valULL(&value);
valULL.SetWindow(m_text);
SECTION("LLONG_MAX")
{
m_text->ChangeValue("9223372036854775807"); // == LLONG_MAX
REQUIRE( valULL.TransferFromWindow() );
CHECK( value == static_cast<wxULongLong_t>(wxINT64_MAX) );
REQUIRE( valULL.TransferToWindow() );
CHECK( m_text->GetValue() == "9223372036854775807" );
}
SECTION("LLONG_MAX+1")
{
m_text->ChangeValue("9223372036854775808"); // == LLONG_MAX + 1
REQUIRE( valULL.TransferFromWindow() );
CHECK( value == static_cast<wxULongLong_t>(wxINT64_MAX) + 1 );
REQUIRE( valULL.TransferToWindow() );
CHECK( m_text->GetValue() == "9223372036854775808" );
}
SECTION("ULLONG_MAX")
{
m_text->ChangeValue("18446744073709551615"); // == ULLONG_MAX
REQUIRE( valULL.TransferFromWindow() );
CHECK( value == wxUINT64_MAX );
REQUIRE( valULL.TransferToWindow() );
CHECK( m_text->GetValue() == "18446744073709551615" );
}
SECTION("ULLONG_MAX+1")
{
m_text->ChangeValue("18446744073709551616"); // == ULLONG_MAX + 1
CHECK( !valULL.TransferFromWindow() );
}
}
TEST_CASE_METHOD(NumValidatorTestCase, "ValNum::TransferFloat", "[valnum]")
{
// We need a locale with point as decimal separator.
wxLocale loc(wxLANGUAGE_ENGLISH_UK, wxLOCALE_DONT_LOAD_DEFAULT);
float value = 0;
wxFloatingPointValidator<float> valFloat(3, &value);
valFloat.SetWindow(m_text);
CHECK( valFloat.TransferToWindow() );
CHECK( m_text->GetValue() == "0.000" );
value = 1.234f;
CHECK( valFloat.TransferToWindow() );
CHECK( m_text->GetValue() == "1.234" );
value = 1.2345678f;
CHECK( valFloat.TransferToWindow() );
CHECK( m_text->GetValue() == "1.235" );
m_text->ChangeValue("foobar");
CHECK( !valFloat.TransferFromWindow() );
m_text->ChangeValue("-234.567");
CHECK( valFloat.TransferFromWindow() );
CHECK( value == -234.567f );
m_text->Clear();
CHECK( !valFloat.TransferFromWindow() );
}
TEST_CASE_METHOD(NumValidatorTestCase, "ValNum::ZeroAsBlank", "[valnum]")
{
long value = 0;
m_text->SetValidator(
wxMakeIntegerValidator(&value, wxNUM_VAL_ZERO_AS_BLANK));
wxValidator * const val = m_text->GetValidator();
CHECK( val->TransferToWindow() );
CHECK( m_text->GetValue() == "" );
value++;
CHECK( val->TransferFromWindow() );
CHECK( value == 0 );
}
TEST_CASE_METHOD(NumValidatorTestCase, "ValNum::NoTrailingZeroes", "[valnum]")
{
// We need a locale with point as decimal separator.
wxLocale loc(wxLANGUAGE_ENGLISH_UK, wxLOCALE_DONT_LOAD_DEFAULT);
double value = 1.2;
m_text->SetValidator(
wxMakeFloatingPointValidator(3, &value, wxNUM_VAL_NO_TRAILING_ZEROES));
wxValidator * const val = m_text->GetValidator();
CHECK( val->TransferToWindow() );
CHECK( m_text->GetValue() == "1.2" );
value = 1.234;
CHECK( val->TransferToWindow() );
CHECK( m_text->GetValue() == "1.234" );
}
#if wxUSE_UIACTIONSIMULATOR
TEST_CASE_METHOD(NumValidatorTestCase, "ValNum::Interactive", "[valnum]")
{
// Set a locale using comma as thousands separator character.
wxLocale loc(wxLANGUAGE_ENGLISH_UK, wxLOCALE_DONT_LOAD_DEFAULT);
m_text->SetValidator(
wxIntegerValidator<unsigned>(NULL, wxNUM_VAL_THOUSANDS_SEPARATOR));
// Create a sibling text control to be able to switch focus and thus
// trigger the control validation/normalization.
wxTextCtrl * const text2 = new wxTextCtrl(m_text->GetParent(), wxID_ANY);
wxON_BLOCK_EXIT_OBJ0( *text2, wxWindow::Destroy );
text2->Move(10, 80); // Just to see it better while debugging...
wxFloatingPointValidator<float> valFloat(3);
valFloat.SetRange(-10., 10.);
text2->SetValidator(valFloat);
wxUIActionSimulator sim;
// Entering '-' in a control with positive range is not allowed.
m_text->SetFocus();
sim.Char('-');
wxYield();
CHECK( m_text->GetValue() == "" );
// Neither is entering '.' or any non-digit character.
sim.Text(".a+/");
wxYield();
CHECK( m_text->GetValue() == "" );
// Entering digits should work though and after leaving the control the
// contents should be normalized.
sim.Text("1234567");
wxYield();
text2->SetFocus();
wxYield();
if ( loc.IsOk() )
CHECK( m_text->GetValue() == "1,234,567" );
else
CHECK( m_text->GetValue() == "1234567" );
// Entering both '-' and '.' in this control should work but only in the
// correct order.
sim.Char('-');
wxYield();
CHECK( text2->GetValue() == "-" );
text2->SetInsertionPoint(0);
sim.Char('.');
wxYield();
CHECK( text2->GetValue() == "-" );
text2->SetInsertionPointEnd();
sim.Char('.');
wxYield();
CHECK( text2->GetValue() == "-." );
// Adding up to three digits after the point should work.
sim.Text("987");
wxYield();
CHECK( text2->GetValue() == "-.987" );
// But no more.
sim.Text("654");
wxYield();
CHECK( text2->GetValue() == "-.987" );
// We can remove one digit and another one though.
sim.Char(WXK_BACK);
sim.Char(WXK_BACK);
sim.Char('6');
wxYield();
CHECK( text2->GetValue() == "-.96" );
// Also test the range constraint.
text2->Clear();
sim.Char('9');
wxYield();
CHECK( text2->GetValue() == "9" );
// Entering a value which is out of range is allowed.
sim.Char('9');
wxYield();
CHECK( text2->GetValue() == "99" );
// But it must be clamped to the valid range on focus loss.
m_text->SetFocus();
wxYield();
CHECK( text2->GetValue() == "10.000" );
// Repeat the test with a too small invalid value.
text2->Clear();
text2->SetFocus();
sim.Text("-22");
wxYield();
CHECK( text2->GetValue() == "-22" );
m_text->SetFocus();
wxYield();
CHECK( text2->GetValue() == "-10.000" );
}
#endif // wxUSE_UIACTIONSIMULATOR