package org.tessell.tests.model.validation.rules;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertThat;
import static org.tessell.model.properties.NewProperty.booleanProperty;
import static org.tessell.model.properties.NewProperty.stringProperty;
import static org.tessell.util.ObjectUtils.eq;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.tessell.model.properties.BooleanProperty;
import org.tessell.model.properties.StringProperty;
import org.tessell.model.validation.events.RuleTriggeredEvent;
import org.tessell.model.validation.events.RuleTriggeredHandler;
import org.tessell.model.validation.rules.Custom;
import org.tessell.model.validation.rules.Length;
import org.tessell.model.validation.rules.Required;
import org.tessell.model.values.DerivedValue;
import org.tessell.model.values.SetValue;
import org.tessell.util.Supplier;
public class RulesTest extends AbstractRuleTest {
public static class Foo {
public StringProperty name = stringProperty("name");
public StringProperty description = stringProperty("description");
}
private final Foo f = new Foo();
@Before
public void listenToName() {
listenTo(f.name);
listenTo(f.description);
}
@Test
public void whenRuleIsValidatedTheStateIsRemembered() {
assertThat(f.name.isValid(), is(true));
f.name.addRule(new Required("name required"));
f.name.addRule(new Length("name length"));
assertThat(f.name.isValid(), is(false));
f.name.set(null);
assertThat(f.name.isValid(), is(false));
}
@Test
public void ruleReassessAfterAdded() {
f.name.touch();
f.name.addRule(new Required("name required"));
assertMessages("name required");
}
@Test
public void whenMultiplesRulesPerPropertyOnlyTheFirstFires() {
f.name.addRule(new Required("name required"));
f.name.addRule(new Length("name length"));
f.name.set(null);
assertMessages("name required");
}
@Test
public void whenMultipleRulesPerPropertyTheSecondCanFireNext() {
f.name.addRule(new Required("name required"));
f.name.addRule(new Length("name length"));
f.name.set(StringUtils.repeat("a", 101));
assertMessages("name length");
// back to a good value
f.name.set("good value");
assertMessages("");
}
@Test
public void reassessingReValidatesForCustomRules() {
final SetValue<Boolean> customLogic = new SetValue<Boolean>("customLogic");
customLogic.set(true);
f.name.addRule(new Custom("custom", customLogic));
f.name.set("a name");
assertMessages("");
// just changing the logic has no way of revalidating
customLogic.set(false);
assertMessages("");
// but reassessing should
f.name.reassess();
assertMessages("custom");
}
@Test
public void customWithSupplier() {
f.name.addRule(new Custom("name cannot be bob", new Supplier<Boolean>() {
public Boolean get() {
return !eq(f.name.get(), "bob");
}
}));
f.name.set("bob");
assertMessages("name cannot be bob");
f.name.set("fred");
assertNoMessages();
}
@Test
public void requiredSubclassWillBeTrackUpstreamValues() {
f.name.addRule(new Required("Required") {
protected boolean isValid() {
return f.description.get() != null;
}
});
f.name.touch();
assertMessages("Required");
f.description.set("some description");
assertNoMessages();
}
@Test
public void untouchingUntriggersTheRule() {
f.name.addRule(new Required("name required"));
f.name.set(null);
assertMessages("name required");
f.name.setTouched(false);
assertMessages("");
}
@Test
public void onlyIfDoesNotTouchDownstreamRules() {
final BooleanProperty b = booleanProperty("b", false);
Required required = new Required("name required");
required.onlyIf(b);
f.name.addRule(required);
// touching b leaves f.name untouched
b.set(true);
assertMessages("");
// but once we explicitly touch it, the rule runs
f.name.touch();
assertMessages("name required");
// and toggling off b fixes it
b.set(false);
assertMessages("");
}
@Test
public void onlyIfDoesNotTouchDownstreamRulesWhenAlreadyTouched() {
final BooleanProperty b = booleanProperty("b");
// start out with b already touched
b.set(true);
Required required = new Required("name required");
required.onlyIf(b);
f.name.addRule(required);
assertThat(f.name.isTouched(), is(false));
}
@Test
public void onlyIfDoesUpstreamTracking() {
final BooleanProperty b = booleanProperty("b", false);
Required r = new Required("name required");
r.onlyIf(b);
f.name.addRule(r);
f.name.touch();
assertMessages("");
b.set(true);
assertMessages("name required");
}
@Test
public void onlyIfAfterAlreadyInvalid() {
Required r = new Required("name required");
f.name.addRule(r);
f.name.touch();
assertMessages("name required");
// now add the only if
final BooleanProperty b = booleanProperty("b", false);
r.onlyIf(b);
// which caused Required to be untriggered
assertMessages("");
}
@Test
public void onlyIfUpgradesNonTouchDerivedProperties() {
Required r = new Required("name required");
f.name.addRule(r);
final BooleanProperty b = booleanProperty("b", true);
r.onlyIf(b);
// touching b at this point doesn't touch f.name
b.touch();
assertMessages("");
// but after explicitly adding it
b.addDerived(f.name);
// now it is touched
assertMessages("name required");
}
@Test
public void onlyIfWithLambda() {
final BooleanProperty b = booleanProperty("b", false);
f.name.addRule(new Required("required").onlyIf(() -> b.isTrue()));
f.name.touch();
assertMessages("");
b.set(true);
assertMessages("required");
}
@Test
public void removeDerivedHandlesMultipleDownstream() {
final BooleanProperty a = booleanProperty("a", true);
final BooleanProperty b = booleanProperty("b", true);
// setup c to depend on b
final BooleanProperty c = booleanProperty(new DerivedValue<Boolean>("c") {
public Boolean get() {
return b.isTrue();
}
});
listenTo(c);
// setup a rule that depends on A and B
c.addRule(new Custom("c failed", new Supplier<Boolean>() {
public Boolean get() {
return a.isTrue() && b.isTrue();
}
}));
// reassess so that c depends on a && b
c.reassess();
assertNoMessages();
// at this point, c is downstream of b because of it's value and the rule
// so, change a to false, which will remove 1 of the downstream tokens from c's upstream
a.set(false);
// so and now if we change b to false
b.set(false);
// c will still have been re-evaled and marked as failing
assertMessages("c failed");
}
@Test
public void rulesCanBeAddedTwice() {
Required r = new Required("name required");
f.name.addRule(r);
f.name.set(null);
assertMessages("name required");
}
@Test
public void ruleHandlersAreFiredBeforeThePropertysHandlers() {
Required r = new Required("name required");
f.name.addRule(r);
final List<String> order = new ArrayList<String>();
r.addRuleTriggeredHandler(new RuleTriggeredHandler() {
public void onTrigger(RuleTriggeredEvent event) {
order.add("rule");
}
});
f.name.addRuleTriggeredHandler(new RuleTriggeredHandler() {
public void onTrigger(RuleTriggeredEvent event) {
order.add("property");
}
});
f.name.touch();
assertThat(order, contains("rule", "property"));
}
@Test
public void whenLengthOnlyThenEmptyIsOkay() {
f.name.addRule(new Length("name length"));
f.name.set(null);
assertNoMessages();
}
@Test
public void tracksUpstreamDependencies() {
f.name.addRule(new Custom("cannot be the description", new Supplier<Boolean>() {
public Boolean get() {
return f.description.get() == null || f.name.get() == null || !f.name.get().equals(f.description.get());
}
}));
f.description.set("a");
f.name.set("a");
assertMessages("cannot be the description");
// just changing description, the rules for a get rerun
f.description.set("b");
assertNoMessages();
}
@Test
public void tracksTwoWayUpstreamDependencies() {
f.name.addRule(new Custom("cannot be the description", new Supplier<Boolean>() {
public Boolean get() {
return f.description.get() == null || f.name.get() == null || !f.name.get().equals(f.description.get());
}
}));
f.description.addRule(new Custom("cannot be the name", new Supplier<Boolean>() {
public Boolean get() {
return f.description.get() == null || f.name.get() == null || !f.name.get().equals(f.description.get());
}
}));
f.description.set("a");
f.name.set("a");
assertMessages("cannot be the description", "cannot be the name");
// just changing description reassesses both properties
f.description.set("b");
assertNoMessages();
// also changing name reassesses both properties
f.name.set("b");
assertMessages("cannot be the description", "cannot be the name");
}
}