package org.robobinding.binder;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyCollection;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.robobinding.binder.PendingAttributesForViewBuilder.aPendingAttributesForView;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robobinding.BindableView;
import org.robobinding.BinderProvider;
import org.robobinding.BindingContext;
import org.robobinding.BindingContextFactory;
import org.robobinding.ItemBinder;
import org.robobinding.PendingAttributesForView;
import org.robobinding.PreInitializingViews;
import org.robobinding.annotation.ItemPresentationModel;
import org.robobinding.attribute.ChildAttributeResolverMappings;
import org.robobinding.attribute.ChildAttributeResolvers;
import org.robobinding.attribute.ResolvedGroupAttributes;
import org.robobinding.itempresentationmodel.ItemContext;
import org.robobinding.presentationmodel.PresentationModelAdapterFactory;
import org.robobinding.test.JavaReflectionPresentationModelObject;
import org.robobinding.viewattribute.AttributeBindingException;
import org.robobinding.viewattribute.grouped.AbstractGroupedViewAttribute;
import org.robobinding.viewattribute.grouped.ChildViewAttributesBuilder;
import org.robobinding.viewattribute.property.OneWayPropertyViewAttribute;
import org.robobinding.viewbinding.BindingAttributeMappings;
import org.robobinding.viewbinding.ViewBinding;
import org.robobinding.widget.abslistview.SparseBooleanArrayUtils;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import android.R;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
/**
*
* @since 1.0
* @version $Revision: 1.0 $
* @author Robert Taylor
*/
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class BindingTest {
private BindingAttributeResolver bindingAttributeResolver;
private BindingContext bindingContext;
private Context context = RuntimeEnvironment.application;
@Before
public void setUp() {
bindingAttributeResolver = new BindingAttributeResolverBuilder().mapView(BuggyCustomView.class, new BuggyCustomViewAttributeMapper()).build();
bindingContext = newBindingContext();
}
@Test
public void whenBindingValidResolvedAttributes_thenShouldNotThrowException() {
ResolvedBindingAttributesForView resolvedBindingAttributes = resolveBindingAttributes(aPendingAttributesForEditText().withAttribute("text", "${name}")
.build());
ViewBindingErrors viewBindingErrors = resolvedBindingAttributes.bindTo(bindingContext);
viewBindingErrors.assertNoErrors();
}
@Test(expected = ViewBindingErrors.class)
public void whenBindingInvalidResolvedPropertyAttributes_thenThrowException() {
ResolvedBindingAttributesForView resolvedBindingAttributes = resolveBindingAttributes(aPendingAttributesForEditText().withAttribute("text",
"${nonExistentProperties}").build());
ViewBindingErrors viewBindingErrors = resolvedBindingAttributes.bindTo(bindingContext);
viewBindingErrors.assertNoErrors();
}
@Test(expected = ViewBindingErrors.class)
public void whenBindingInvalidResolvedCommandAttributes_thenThrowException() {
ResolvedBindingAttributesForView resolvedBindingAttributes = resolveBindingAttributes(aPendingAttributesForEditText().withAttribute("onTextChanged",
"setName").build());
ViewBindingErrors viewBindingErrors = resolvedBindingAttributes.bindTo(bindingContext);
viewBindingErrors.assertNoErrors();
}
@Test
public void whenBindingMultipleInvalidResolvedAttributes_thenThrowExceptionReferringToEachOne() {
ResolvedBindingAttributesForView resolvedBindingAttributes = resolveBindingAttributes(aPendingAttributesForEditText()
.withAttribute("visibility", "{nonExistentProperty}").withAttribute("onTextChanged", "setName").build());
try {
ViewBindingErrors viewBindingErrors = resolvedBindingAttributes.bindTo(bindingContext);
viewBindingErrors.assertNoErrors();
fail("Expected exception to be thrown");
} catch (ViewBindingErrors e) {
assertHasAttributeError(e, "visibility");
assertHasAttributeError(e, "onTextChanged");
assertThat(e.numErrors(), is(2));
}
}
@Test
public void whenBindingMultipleInvalidResolvedGroupChildAttributes_thenThrowExceptionReferringToEachOne() {
ResolvedBindingAttributesForView resolvedBindingAttributes = resolveBindingAttributes(aPendingAttributesForAdapterView()
.withAttribute("source", "{nonExistentProperty}").withAttribute("itemLayout", "@layout/non_existent_layout").build());
try {
ViewBindingErrors viewBindingErrors = resolvedBindingAttributes.bindTo(bindingContext);
viewBindingErrors.assertNoErrors();
fail("Expected exception to be thrown");
} catch (ViewBindingErrors e) {
assertHasAttributeError(e, "source");
assertHasAttributeError(e, "itemLayout");
assertThat(e.numErrors(), is(2));
}
}
@Test
public void whenBindingMultipleInvalidResolvedSubViewAttributes_thenThrowExceptionReferringToFirstFailure() {
ResolvedBindingAttributesForView resolvedBindingAttributes = resolveBindingAttributes(aPendingAttributesForAdapterView()
.withAttribute("itemLayout", "{layout}").withAttribute("source", "{dataSource}").withAttribute("footerLayout", "@layout/non_existent_layout")
.withAttribute("footerVisibility", "{nonExistentProperty}").withAttribute("footerPresentationModel", "{nonExistentProperty}").build());
try {
ViewBindingErrors viewBindingErrors = resolvedBindingAttributes.bindTo(bindingContext);
viewBindingErrors.assertNoErrors();
fail("Expected exception to be thrown");
} catch (ViewBindingErrors e) {
assertHasAttributeError(e, "footerLayout");
assertThat(e.numErrors(), is(1));
}
}
@Ignore
@Test(expected = ProgrammingError.class)
public void whenAnUnexpectedExceptionIsThrownDuringBinding_thenErrorShouldNotBeSuppressed() {
ResolvedBindingAttributesForView resolvedBindingAttributes = resolveBindingAttributes(aPendingAttributesForBuggyCustomView().withAttribute(
BuggyCustomView.BUGGY_PROPERTY_ATTRIBUTE, "{name}").build());
resolvedBindingAttributes.bindTo(bindingContext);
resolvedBindingAttributes.preinitializeView(bindingContext);
}
@Test(expected = ProgrammingError.class)
public void whenAnUnexpectedExceptionIsThrownDuringGroupChildAttributeBinding_thenErrorShouldNotBeSuppressed() {
ResolvedBindingAttributesForView resolvedBindingAttributes = resolveBindingAttributes(aPendingAttributesForBuggyCustomView().withAttribute(
BuggyCustomView.BUGGY_GROUP_CHILD_ATTRIBUTE, "{name}").build());
resolvedBindingAttributes.bindTo(bindingContext);
}
@Test
public void whenBindingWithPreInitializingViews_thenInitializedValueShouldNotBeErased() {
ListView listView = new ListView(context);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
ResolvedBindingAttributesForView resolvedBindingAttributes = resolveBindingAttributes(aPendingAttributesForView(listView)
.withAttribute("checkedItemPositions", "{checkedItemPositions}").withAttribute("itemLayout", "{layout}")
.withAttribute("source", "{dataSource}").build());
ViewBindingErrors viewBindingErrors = resolvedBindingAttributes.bindTo(bindingContext);
viewBindingErrors.assertNoErrors();
resolvedBindingAttributes.preinitializeView(bindingContext);
viewBindingErrors.assertNoErrors();
assertThat(SparseBooleanArrayUtils.toSet(listView.getCheckedItemPositions()), equalTo((Set<Integer>) newHashSet(0, 2)));
}
private PendingAttributesForViewBuilder aPendingAttributesForEditText() {
EditText editText = new EditText(context);
return aPendingAttributesForView(editText);
}
private PendingAttributesForViewBuilder aPendingAttributesForAdapterView() {
AdapterView<?> adapterView = new ListView(context);
return aPendingAttributesForView(adapterView);
}
private PendingAttributesForViewBuilder aPendingAttributesForBuggyCustomView() {
BuggyCustomView buggyCustomView = new BuggyCustomView(context);
return aPendingAttributesForView(buggyCustomView);
}
private ResolvedBindingAttributesForView resolveBindingAttributes(PendingAttributesForView pendingAttributesForView) {
ViewResolutionResult resolutionResult = bindingAttributeResolver.resolve(pendingAttributesForView);
resolutionResult.assertNoErrors();
return resolutionResult.getResolvedBindingAttributes();
}
private void assertHasAttributeError(ViewBindingErrors e, String attribute) {
Collection<AttributeBindingException> attributeErrors = e.getAttributeErrors();
for (AttributeBindingException attributeError : attributeErrors) {
if (attributeError.getAttributeName().equals(attribute)) {
return;
}
}
fail("Binding error for " + attribute + " was not reported");
}
@SuppressWarnings("unchecked")
private BindingContext newBindingContext() {
BinderProvider binderProvider = mock(BinderProvider.class);
BindableView bindableView = mock(BindableView.class);
when(bindableView.getRootView()).then(new Answer<View>() {
@Override
public View answer(InvocationOnMock invocation) throws Throwable {
return new View(context);
}
});
ItemBinder itemBinder = mock(ItemBinder.class);
when(itemBinder.inflateWithoutAttachingToRoot(anyInt(), anyCollection(), any(ViewGroup.class))).thenReturn(bindableView);
when(binderProvider.createItemBinder(any(BindingContextFactory.class))).thenReturn(itemBinder);
return new BindingContext(binderProvider, context,
new PresentationModelAdapterFactory().create(new JavaReflectionPresentationModelObject(new PresentationModelForTest())),
PreInitializingViews.initial(true));
}
public static class PresentationModelForTest {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ItemPresentationModel(ItemPresentationModelForTest.class)
public List<String> getDataSource() {
return newArrayList("string1", "string2", "string3");
}
public Set<Integer> getcheckedItemPositions() {
return newHashSet(0, 2);
}
public int getLayout() {
return R.layout.simple_list_item_1;
}
}
public static class ItemPresentationModelForTest implements org.robobinding.itempresentationmodel.ItemPresentationModel<String> {
@Override
public void updateData(String bean, ItemContext itemContext) {
}
}
private static class BuggyCustomView extends View {
static final String BUGGY_PROPERTY_ATTRIBUTE = "buggyPropertyAttribute";
static final String BUGGY_GROUP_CHILD_ATTRIBUTE = "buggyGroupChildAttribute";
public BuggyCustomView(Context context) {
super(context);
}
}
private static class BuggyCustomViewAttributeMapper implements ViewBinding<BuggyCustomView> {
@Override
public void mapBindingAttributes(BindingAttributeMappings<BuggyCustomView> mappings) {
mappings.mapOneWayProperty(BuggyPropertyAttribute.class, BuggyCustomView.BUGGY_PROPERTY_ATTRIBUTE);
mappings.mapGroupedAttribute(BuggyGroupedAttribute.class, BuggyCustomView.BUGGY_GROUP_CHILD_ATTRIBUTE);
}
}
public static class BuggyPropertyAttribute implements OneWayPropertyViewAttribute<BuggyCustomView, String> {
@Override
public void updateView(BuggyCustomView view, String newValue) {
throw new ProgrammingError();
}
}
public static class BuggyGroupedAttribute extends AbstractGroupedViewAttribute<BuggyCustomView> {
@Override
public void mapChildAttributeResolvers(ChildAttributeResolverMappings resolverMappings) {
resolverMappings.map(ChildAttributeResolvers.propertyAttributeResolver(), BuggyCustomView.BUGGY_GROUP_CHILD_ATTRIBUTE);
}
@Override
public void setupChildViewAttributes(BuggyCustomView view, ChildViewAttributesBuilder<BuggyCustomView> childViewAttributesBuilder) {
throw new ProgrammingError();
}
@Override
public void validateResolvedChildAttributes(ResolvedGroupAttributes groupAttributes) {
}
}
@SuppressWarnings("serial")
public static class ProgrammingError extends RuntimeException {
}
}