package org.tessell.tests.model.properties; import static joist.util.Copy.list; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.tessell.model.properties.NewProperty.*; import java.util.ArrayList; import java.util.List; import org.junit.Test; import org.tessell.gwt.user.client.ui.StubTextBox; import org.tessell.model.dsl.Binder; import org.tessell.model.events.PropertyChangedEvent; import org.tessell.model.events.PropertyChangedHandler; import org.tessell.model.properties.*; 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.Required; import org.tessell.model.values.DerivedValue; import org.tessell.model.values.SetValue; import org.tessell.tests.model.validation.rules.AbstractRuleTest; public class PropertyTest extends AbstractRuleTest { @Test public void twoWayDerived() { final IntegerProperty a = integerProperty("a", 3); final IntegerProperty b = integerProperty("b", 2); listenTo(a); listenTo(b); a.touch(); b.touch(); a.addRule(new Custom("a must be greater than b", new DerivedValue<Boolean>() { public Boolean get() { return a.get() > b.get(); } })); b.addRule(new Custom("b must be within 5 of a", new DerivedValue<Boolean>() { public Boolean get() { return Math.abs(a.get() - b.get()) <= 5; } })); assertMessages(""); a.set(-10); assertMessages("a must be greater than b", "b must be within 5 of a"); } @Test public void validationHappensBeforeChange() { final IntegerProperty a = integerProperty("a", 10); a.addRule(new Custom("a must be greater than 5", new DerivedValue<Boolean>() { public Boolean get() { return a.get() != null && a.get() > 5; } })); final Boolean[] wasInvalidOnChange = { null }; a.addPropertyChangedHandler(new PropertyChangedHandler<Integer>() { public void onPropertyChanged(PropertyChangedEvent<Integer> event) { wasInvalidOnChange[0] = a.isValid() == false; } }); a.set(1); assertThat(wasInvalidOnChange[0], is(true)); } @Test public void validationOfDerivedValuesHappensBeforeChange() { final IntegerProperty a = integerProperty("a"); final IntegerProperty b = integerProperty("b"); b.addRule(new Custom("b must be greater than a", new DerivedValue<Boolean>() { public Boolean get() { return b.get() == null || a.get() == null || b.get() > a.get(); } })); // set with good values a.set(1); b.set(2); final Boolean[] asWasInvalid = { null }; a.addPropertyChangedHandler(new PropertyChangedHandler<Integer>() { public void onPropertyChanged(PropertyChangedEvent<Integer> event) { asWasInvalid[0] = b.isValid() == false; } }); a.set(3); assertThat(asWasInvalid[0], is(true)); } @Test public void wasValidIsSetBeforeRulesAreTriggered() { final IntegerProperty a = integerProperty("a").req(); final Boolean[] wasValid = { null }; a.addRuleTriggeredHandler(new RuleTriggeredHandler() { public void onTrigger(RuleTriggeredEvent event) { wasValid[0] = a.isValid() == true; } }); a.touch(); assertThat(wasValid[0], is(false)); } @Test public void derivedWatchesGetValueMethod() { final IntegerProperty a = integerProperty("a"); final BooleanProperty b = booleanProperty(new DerivedValue<Boolean>("not null") { public Boolean get() { return a.getValue() != null; } }); CountChanges c = CountChanges.on(b); a.set(1); assertThat(c.changes, is(1)); } @Test public void derivedWatchesIsValid() { final IntegerProperty a = integerProperty("a").req(); final BooleanProperty b = booleanProperty(new DerivedValue<Boolean>("was valid") { public Boolean get() { return a.isValid() == true; } }); CountChanges c = CountChanges.on(b); a.set(1); assertThat(c.changes, is(1)); } @Test public void derivedWatchesValid() { final IntegerProperty a = integerProperty("a").req(); final BooleanProperty b = booleanProperty(new DerivedValue<Boolean>("was valid") { public Boolean get() { return a.valid().get() == true; } }); CountChanges c = CountChanges.on(b); a.set(1); assertThat(c.changes, is(1)); } @Test public void derivedWatchesTouched() { final IntegerProperty a = integerProperty("a"); final BooleanProperty b = booleanProperty(new DerivedValue<Boolean>("was touched") { public Boolean get() { return a.touched().get() == true; } }); CountChanges c = CountChanges.on(b); a.set(1); assertThat(c.changes, is(1)); } @Test public void derivedWatchesIsTouched() { final IntegerProperty a = integerProperty("a"); final BooleanProperty b = booleanProperty(new DerivedValue<Boolean>("not null") { public Boolean get() { return a.isTouched(); } }); CountChanges c = CountChanges.on(b); a.set(1); assertThat(c.changes, is(1)); } @Test public void setInitialLeavesPropertiesUnTouched() { final IntegerProperty a = integerProperty("a"); a.setInitialValue(1); assertThat(a.isTouched(), is(false)); } @Test public void setIfNullLeavesPropertiesUnTouched() { final IntegerProperty a = integerProperty("a"); a.setIfNull(1); assertThat(a.isTouched(), is(false)); } @Test public void setDefaultValueChangesValueRightAwayIfNull() { final IntegerProperty a = integerProperty("a"); a.setDefaultValue(1); assertThat(a.get(), is(1)); assertThat(a.isTouched(), is(false)); } @Test public void setDefaultValueDoesNotChangeValueRightAwayIfNotNull() { final IntegerProperty a = integerProperty("a", 0); a.setDefaultValue(1); assertThat(a.get(), is(0)); assertThat(a.isTouched(), is(false)); } @Test public void setDefaultValueChangesNullWhenSetLater() { final IntegerProperty a = integerProperty("a", 0); a.setDefaultValue(1); a.set(null); assertThat(a.get(), is(1)); } @Test public void setDefaultValueChangesNullWhenSetInitialLater() { final IntegerProperty a = integerProperty("a"); a.setDefaultValue(1); a.setInitialValue(null); assertThat(a.get(), is(1)); } @Test public void setDefaultValueFiresChange() { final BooleanProperty a = booleanProperty("a"); CountChanges c = CountChanges.on(a); a.setDefaultValue(true); assertThat(c.changes, is(1)); a.set(null); assertThat(c.changes, is(2)); } @Test public void setDefaultValueWatchesForOutOfBandSets() { final SetValue<Boolean> b = new SetValue<Boolean>("b"); final BooleanProperty p = booleanProperty(b); p.setDefaultValue(true); assertThat(b.get(), is(true)); // now have b get changed out of band b.set(null); p.reassess(); assertThat(p.get(), is(true)); assertThat(b.get(), is(true)); } @Test(expected = IllegalStateException.class) public void setDefaultValueForReadOnlyProperty() { final IntegerProperty a = integerProperty(new DerivedValue<Integer>("a") { public Integer get() { return 1; } }); a.setDefaultValue(2); } @Test public void testIsValue() { final StringProperty s = stringProperty("s"); final Property<Boolean> b = s.is("foo"); CountChanges c = CountChanges.on(b); assertThat(b.getValue(), is(false)); assertThat(b.isTouched(), is(false)); s.set("foo"); assertThat(b.getValue(), is(true)); assertThat(s.get(), is("foo")); assertThat(c.changes, is(1)); s.set("bar"); assertThat(b.getValue(), is(false)); assertThat(s.get(), is("bar")); assertThat(c.changes, is(2)); b.setValue(true); assertThat(s.get(), is("foo")); assertThat(c.changes, is(3)); b.setValue(false); assertThat(s.get(), is(nullValue())); assertThat(c.changes, is(4)); } @Test public void testIsOther() { final StringProperty s1 = stringProperty("s1", "foo"); final StringProperty s2 = stringProperty("s2", "bar"); final Property<Boolean> b = s1.is(s2); CountChanges c = CountChanges.on(b); assertThat(b.getValue(), is(false)); assertThat(b.isTouched(), is(false)); s1.set("bar"); assertThat(b.getValue(), is(true)); assertThat(c.changes, is(1)); assertThat(s1.get(), is("bar")); assertThat(s2.get(), is("bar")); s1.set("foo"); assertThat(b.getValue(), is(false)); assertThat(c.changes, is(2)); assertThat(s1.get(), is("foo")); assertThat(s2.get(), is("bar")); s2.set("foo"); assertThat(b.getValue(), is(true)); assertThat(c.changes, is(3)); assertThat(s1.get(), is("foo")); assertThat(s2.get(), is("foo")); b.setValue(false); assertThat(c.changes, is(4)); assertThat(s1.get(), is(nullValue())); assertThat(s2.get(), is("foo")); b.setValue(true); assertThat(c.changes, is(5)); assertThat(s1.get(), is("foo")); assertThat(s2.get(), is("foo")); } @Test public void testIsCondition() { final StringProperty s = stringProperty("s"); final Property<Boolean> b = s.is(new Condition<String>() { public boolean evaluate(String value) { return value != null && value.length() > 3; } }); CountChanges c = CountChanges.on(b); assertThat(b.getValue(), is(false)); assertThat(b.isTouched(), is(false)); s.set("food"); assertThat(b.getValue(), is(true)); assertThat(b.isTouched(), is(true)); assertThat(c.changes, is(1)); s.set("bar"); assertThat(b.getValue(), is(false)); assertThat(b.isTouched(), is(true)); assertThat(c.changes, is(2)); assertThat(b.isReadOnly(), is(true)); } @Test public void testIsConditionWithMultipleUpstreamValues() { final StringProperty s1 = stringProperty("s1", "a"); final StringProperty s2 = stringProperty("s2", "b"); final Property<Boolean> oneIsBigger = s1.is(new Condition<String>() { public boolean evaluate(String value) { return value.length() > s2.get().length(); } }); CountChanges c = CountChanges.on(oneIsBigger); assertThat(oneIsBigger.getValue(), is(false)); assertThat(oneIsBigger.isTouched(), is(false)); // changing s1 updates the value s1.set("foo"); assertThat(oneIsBigger.getValue(), is(true)); assertThat(oneIsBigger.isTouched(), is(true)); assertThat(c.changes, is(1)); // changing s2 also updates the value s2.set("food"); assertThat(oneIsBigger.getValue(), is(false)); assertThat(oneIsBigger.isTouched(), is(true)); assertThat(c.changes, is(2)); } @Test public void testIsReadOnlyValue() { final StringProperty s = stringProperty(new DerivedValue<String>("s") { public String get() { return "s"; } }); final Property<Boolean> b = s.is("foo"); assertThat(b.getValue(), is(false)); // s is read-only, so this is a no-op b.setValue(true); assertThat(s.get(), is("s")); assertThat(b.getValue(), is(false)); } @Test public void testIsReadOnlyProperty() { final StringProperty s = stringProperty(new DerivedValue<String>("s") { public String get() { return "s"; } }); final Property<Boolean> b = s.is(stringProperty("s2", "t")); assertThat(b.getValue(), is(false)); // s is read-only, so this is a no-op b.setValue(true); assertThat(s.get(), is("s")); assertThat(b.getValue(), is(false)); } @Test public void testAsString() { // a simple class that overrides toString class Foo { private final String value; public Foo(String value) { this.value = value; } @Override public String toString() { return value; } } final BasicProperty<Foo> foo = basicProperty("foo"); final Property<String> s = foo.asString(); final CountChanges count = CountChanges.on(s); assertThat(s.get(), is(nullValue())); foo.set(new Foo("bar")); assertThat(s.get(), is("bar")); assertThat(count.changes, is(1)); } @Test public void canAddTemporaryErrors() { final BasicProperty<String> s = basicProperty("s"); listenTo(s); s.setTemporaryError("Something bad"); assertThat(s.isTouched(), is(true)); assertMessages("Something bad"); } @Test public void canClearTemporary() { final BasicProperty<String> s = basicProperty("s"); s.setTouched(true); listenTo(s); s.setTemporaryError("Something bad"); // when the code explicitly requests to clear s.clearTemporaryError(); // then we untouch to let the user re-enter a valid value assertThat(s.isTouched(), is(false)); // and so no messages are shown assertNoMessages(); // and when they re-touch s.setTouched(true); // then the temporary error doesn't reappear assertNoMessages(); } @Test public void canChangeTemporaryError() { final BasicProperty<String> s = basicProperty("s"); s.setTouched(true); listenTo(s); s.setTemporaryError("Something bad 1"); s.setTemporaryError("Something bad 2"); assertMessages("Something bad 2"); } @Test public void temporaryErrorClearsOnChange() { final BasicProperty<String> s = basicProperty("s"); s.setTouched(true); listenTo(s); s.setTemporaryError("Something bad 1"); s.set("new value"); assertNoMessages(); } @Test public void temporaryErrorTakesPrecedenceOverOtherRules() { final BasicProperty<String> s = basicProperty("s"); s.req(); listenTo(s); s.setTouched(true); assertMessages("S is required"); s.setTemporaryError("Something bad"); assertMessages("Something bad"); } @Test public void otherRulesComeBackWhenTemporaryErrorIsCleared() { final BasicProperty<String> s = basicProperty("s"); s.req(); listenTo(s); s.setTouched(true); assertMessages("S is required"); s.setTemporaryError("Something bad"); // when the code explicitly requests clearTemporaryError s.clearTemporaryError(); // then we untouch to let the user re-enter, so no errors are shown assertNoMessages(); // when they do retouch s.setTouched(true); // then we show the usual error messages assertMessages("S is required"); } @Test public void temporaryErrorIsClearedByTouching() { final BasicProperty<String> s = basicProperty("s"); listenTo(s); s.setTouched(true); s.setTemporaryError("Something bad"); assertMessages("Something bad"); s.setTouched(true); assertNoMessages(); } @Test public void orIfNull() { final Property<String> a = basicProperty("s"); final Property<String> b = a.orIfNull("unset"); assertThat(b.get(), is("unset")); a.set("foo"); assertThat(b.get(), is("foo")); a.set(null); assertThat(b.get(), is("unset")); } @Test public void validFiresChangeEvents() { final BasicProperty<String> s = basicProperty("s"); CountChanges c = CountChanges.on(s.valid()); assertThat(c.changes, is(0)); s.req(); assertThat(c.changes, is(1)); s.set("asdf"); assertThat(c.changes, is(2)); } @Test public void validFiresChangeEventsForConditionalRules() { final BasicProperty<String> s = basicProperty("s"); // make a conditional rule Required rule = new Required(); final BooleanProperty condition = booleanProperty("condition", false); rule.onlyIf(condition); s.addRule(rule); CountChanges c = CountChanges.on(s.valid()); assertThat(c.changes, is(0)); assertThat(s.isValid(), is(true)); s.set(null); assertThat(c.changes, is(0)); assertThat(s.isValid(), is(true)); condition.set(true); assertThat(c.changes, is(1)); assertThat(s.isValid(), is(false)); } @Test public void isSet() { final BasicProperty<String> s = basicProperty("s"); final Property<Boolean> set = s.isSet(); CountChanges c = CountChanges.on(set); s.set("asdf"); assertThat(c.changes, is(1)); assertThat(set.get(), is(true)); s.set(null); assertThat(c.changes, is(2)); assertThat(set.get(), is(false)); assertThat(set.isReadOnly(), is(true)); } @Test public void nowAndOnChange() { final List<Integer> values = new ArrayList<Integer>(); final IntegerProperty a = integerProperty("a", 10); a.nowAndOnChange(new PropertyValueHandler<Integer>() { public void onValue(Integer value) { values.add(value); } }); assertThat(values, contains(10)); a.set(11); assertThat(values, contains(10, 11)); } @Test public void testInvalidForUntouchedIsFalse() { final StringProperty s = stringProperty("s").numeric(); BooleanProperty invalid = s.invalid(); assertThat(invalid.get(), is(false)); assertThat(s.valid().get(), is(true)); } @Test public void testInvalidForSetInvalidAndUntouncedIsFalse() { final StringProperty s = stringProperty("s").numeric(); BooleanProperty invalid = s.invalid(); s.setInitialValue("foo"); assertThat(invalid.get(), is(false)); assertThat(s.valid().get(), is(false)); } @Test public void testInvalidForSetInvalidAndTouncedIsTrue() { final StringProperty s = stringProperty("s").numeric(); BooleanProperty invalid = s.invalid(); s.set("foo"); assertThat(invalid.get(), is(true)); assertThat(s.valid().get(), is(false)); } @Test public void testInvalidForSetValidAndTouncedIsFalse() { final StringProperty s = stringProperty("s").numeric(); BooleanProperty invalid = s.invalid(); s.set("1"); assertThat(invalid.get(), is(false)); assertThat(s.valid().get(), is(true)); } @Test public void changedWorksWhenSetIsCalledAfterListening() { final IntegerProperty a = integerProperty("a", null); final Property<Boolean> b = a.changed(); a.set(2); assertThat(b.get(), is(true)); a.set(null); assertThat(b.get(), is(false)); } @Test public void changedWorksWhenSetIsCalledBeforeListening() { final IntegerProperty a = integerProperty("a", null); a.set(2); final Property<Boolean> b = a.changed(); assertThat(b.get(), is(true)); a.set(null); assertThat(b.get(), is(false)); } @Test public void changedIgnoresSetInitialWhenSetIsCalledAfterListening() { final IntegerProperty a = integerProperty("a", null); final Property<Boolean> b = a.changed(); a.setInitialValue(2); assertThat(b.get(), is(false)); a.set(2); assertThat(b.get(), is(false)); a.set(3); assertThat(b.get(), is(true)); } @Test public void changedIgnoresSetInitialWhenSetIsCalledBeforeListening() { final IntegerProperty a = integerProperty("a", null); a.setInitialValue(2); final Property<Boolean> b = a.changed(); assertThat(b.get(), is(false)); a.set(2); assertThat(b.get(), is(false)); a.set(3); assertThat(b.get(), is(true)); } @Test public void changedWorksWhenSetWithTouchFalseAndSetIsCalledAfterListening() { final IntegerProperty a = integerProperty("a", null); final Property<Boolean> b = a.changed(); a.set(2, false); assertThat(b.get(), is(true)); a.set(null, false); assertThat(b.get(), is(false)); } @Test public void changedWorksWhenSetWithTouchFalseAndSetIsCalledBeforeListening() { final IntegerProperty a = integerProperty("a", null); a.set(2, false); final Property<Boolean> b = a.changed(); assertThat(b.get(), is(true)); a.set(null); assertThat(b.get(), is(false)); } @Test public void changedDetectedDuringReassessWhenSetIsCalledAfterListening() { SetValue<Integer> v = new SetValue<Integer>("a", null); final IntegerProperty a = integerProperty(v); final Property<Boolean> b = a.changed(); v.set(2); a.reassess(); assertThat(b.get(), is(true)); v.set(null); a.reassess(); assertThat(b.get(), is(false)); } @Test public void changedDetectedDuringReassessWhenSetIsCalledBeforeListening() { SetValue<Integer> v = new SetValue<Integer>("a", null); final IntegerProperty a = integerProperty(v); v.set(2); final Property<Boolean> b = a.changed(); a.reassess(); assertThat(b.get(), is(true)); v.set(null); a.reassess(); assertThat(b.get(), is(false)); } @Test public void changedWorksForLists() { final ListProperty<Integer> a = listProperty("a"); final Property<Boolean> b = a.changed(); assertThat(b.get(), is(false)); a.setInitialValue(list(1, 2)); assertThat(b.get(), is(false)); a.remove(2); assertThat(b.get(), is(true)); a.add(2); assertThat(b.get(), is(false)); a.set(null); assertThat(b.get(), is(true)); a.set(list(1, 2)); assertThat(b.get(), is(false)); } @Test public void changedIgnoresDtoModelMerges() { SomeModel m = new SomeModel(new SomeDto(null, false)); final Property<Boolean> b = m.name.changed(); m.merge(new SomeDto("foo", true)); assertThat(b.get(), is(false)); m.name.set("bar"); assertThat(b.get(), is(true)); m.merge(new SomeDto("zaz", true)); assertThat(b.get(), is(false)); } @Test public void changedWithBinding() { Binder b = new Binder(); StringProperty s = stringProperty("s"); StubTextBox box = new StubTextBox(); b.bind(s).to(box); final Property<Boolean> c = s.changed(); assertThat(c.get(), is(false)); box.typeEach("foo"); assertThat(c.get(), is(true)); s.set("foo"); assertThat(c.get(), is(true)); } }