Add manual Components.Validator::validator method

Support manual validation where needed, add unit tests.

This also slightly changes the behaviour of non-required field, since
an empty non-required field should be valid.
This commit is contained in:
Michael Gratton 2019-11-20 14:45:04 +11:00 committed by Michael James Gratton
parent c9fc877d12
commit ca6cbecb83
4 changed files with 127 additions and 10 deletions

View file

@ -48,7 +48,9 @@ public class Components.Validator : GLib.Object {
/** The cause of a validity check being required. */
public enum Trigger {
/** The entry's contents changed */
/** A manual validation was requested via {@link validate}. */
MANUAL,
/** The entry's contents changed. */
CHANGED,
/** The entry lost the keyboard focus. */
LOST_FOCUS,
@ -162,6 +164,17 @@ public class Components.Validator : GLib.Object {
this.pulse_timer.reset();
}
/**
* Triggers a validation of the entry.
*
* In the case of an asynchronous validation implementations,
* result of the validation will be known sometime after this call
* has completed.
*/
public void validate() {
validate_entry(MANUAL);
}
/**
* Called to validate the target entry's value.
*
@ -187,7 +200,7 @@ public class Components.Validator : GLib.Object {
* By default, this always returns {@link Validity.VALID}, making
* it useful for required, but otherwise free-form fields only.
*/
protected virtual Validity validate(string value, Trigger reason) {
protected virtual Validity do_validate(string value, Trigger reason) {
return Validity.VALID;
}
@ -255,12 +268,10 @@ public class Components.Validator : GLib.Object {
string value = this.target.get_text();
Validity new_state = this.state;
if (Geary.String.is_empty_or_whitespace(value)) {
new_state = this.is_required
? Validity.EMPTY : Validity.INDETERMINATE;
new_state = this.is_required ? Validity.EMPTY : Validity.VALID;
} else {
new_state = validate(value, reason);
new_state = do_validate(value, reason);
}
update_state(new_state, reason);
}
@ -384,8 +395,8 @@ public class Components.EmailValidator : Validator {
}
protected override Validator.Validity validate(string value,
Validator.Trigger reason) {
protected override Validator.Validity do_validate(string value,
Validator.Trigger reason) {
return Geary.RFC822.MailboxAddress.is_valid_address(value)
? Validator.Validity.VALID : Validator.Validity.INVALID;
}
@ -435,8 +446,8 @@ public class Components.NetworkAddressValidator : Validator {
}
public override Validator.Validity validate(string value,
Validator.Trigger reason) {
public override Validator.Validity do_validate(string value,
Validator.Trigger reason) {
if (this.cancellable != null) {
this.cancellable.cancel();
}

View file

@ -0,0 +1,104 @@
/*
* Copyright 2019 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Components.ValidatorTest : TestCase {
private Gtk.Entry? entry = null;
public ValidatorTest() {
base("Components.ValidatorTest");
add_test("manual_empty", manual_empty);
add_test("manual_valid", manual_valid);
add_test("manual_not_required", manual_not_required);
}
public override void set_up() {
this.entry = new Gtk.Entry();
}
public override void tear_down() {
this.entry = null;
}
public void manual_empty() throws GLib.Error {
Validator test_article = new Validator(this.entry);
bool finished = false;
Validator.Trigger? reason = null;
Validator.Validity? prev_state = null;
test_article.state_changed.connect((r, p) => {
finished = true;
reason = r;
prev_state = p;
});
test_article.validate();
while (!finished) {
this.main_loop.iteration(true);
}
assert_false(test_article.is_valid);
assert_true(test_article.state == EMPTY);
assert_true(reason == MANUAL);
assert_true(prev_state == INDETERMINATE);
}
public void manual_valid() throws GLib.Error {
this.entry.text = "OHHAI";
Validator test_article = new Validator(this.entry);
bool finished = false;
Validator.Trigger? reason = null;
Validator.Validity? prev_state = null;
test_article.state_changed.connect((r, p) => {
finished = true;
reason = r;
prev_state = p;
});
test_article.validate();
while (!finished) {
this.main_loop.iteration(true);
}
assert_true(test_article.is_valid);
assert_true(test_article.state == VALID);
assert_true(reason == MANUAL);
assert_true(prev_state == INDETERMINATE);
}
public void manual_not_required() throws GLib.Error {
Validator test_article = new Validator(this.entry);
test_article.is_required = false;
bool finished = false;
Validator.Trigger? reason = null;
Validator.Validity? prev_state = null;
test_article.state_changed.connect((r, p) => {
finished = true;
reason = r;
prev_state = p;
});
test_article.validate();
while (!finished) {
this.main_loop.iteration(true);
}
assert_true(test_article.is_valid);
assert_true(test_article.state == VALID);
assert_true(reason == MANUAL);
assert_true(prev_state == INDETERMINATE);
}
}

View file

@ -84,6 +84,7 @@ geary_test_client_sources = [
'client/application/application-configuration-test.vala',
'client/components/client-web-view-test.vala',
'client/components/client-web-view-test-case.vala',
'client/components/components-validator-test.vala',
'client/composer/composer-web-view-test.vala',
'client/util/util-avatar-test.vala',
'client/util/util-cache-test.vala',

View file

@ -53,6 +53,7 @@ int main(string[] args) {
client.add_suite(new Application.ConfigurationTest().get_suite());
client.add_suite(new ClientWebViewTest().get_suite());
client.add_suite(new Composer.WebViewTest().get_suite());
client.add_suite(new Components.ValidatorTest().get_suite());
client.add_suite(new Util.Avatar.Test().get_suite());
client.add_suite(new Util.Cache.Test().get_suite());
client.add_suite(new Util.Email.Test().get_suite());