package com.airbnb.epoxy; import android.view.View; import com.airbnb.epoxy.EpoxyController.Interceptor; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import java.util.List; @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21) public class EpoxyModelValidationTest { @Rule public ExpectedException thrown = ExpectedException.none(); static class Controller extends EpoxyController { @Override protected void buildModels() { } } @Test public void testModelCannotBeAddedTwice() { thrown.expect(IllegalEpoxyUsage.class); thrown.expectMessage("This model was already added to the controller at position 0"); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { Model model = new Model_().id(1); model.addTo(this); model.addTo(this); } }; controller.requestModelBuild(); } @Test public void addToOnlyValidInsideBuildModels() { thrown.expect(IllegalEpoxyUsage.class); thrown.expectMessage("You can only add models inside the `buildModels` methods"); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { } }; new Model_() .id(1) .addTo(controller); } @Test public void addModelOnlyValidInsideBuildModels() { thrown.expect(IllegalEpoxyUsage.class); thrown.expectMessage("You can only add models inside the `buildModels` methods"); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { } }; controller.add(new Model_().id(1)); } @Test public void cannotCallBuildModelsDirectly() { thrown.expect(IllegalEpoxyUsage.class); thrown.expectMessage("Call `requestModelBuild` instead"); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { new Model_() .id(1) .addTo(this); } }; controller.buildModels(); } @Test public void mustSpecifyModelIdInController() { thrown.expect(IllegalEpoxyUsage.class); thrown.expectMessage("You must set an id on a model"); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { new Model_() .addTo(this); } }; controller.requestModelBuild(); } @Test public void hideModelNotAllowedInController() { thrown.expect(IllegalEpoxyUsage.class); thrown.expectMessage("You cannot hide a model"); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { new Model_() .id(1) .hide() .addTo(this); } }; controller.requestModelBuild(); } @Test public void cannotChangeIdAfterAddingModelToController() { thrown.expect(IllegalEpoxyUsage.class); thrown.expectMessage("Cannot change a model's id"); final Model model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.requestModelBuild(); model.id(2); } @Test public void mutationNotAllowedAfterModelIsAdded_reset() { thrown.expect(ImmutableModelException.class); thrown.expectMessage("Epoxy attribute fields on a model cannot be changed"); final Model model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.requestModelBuild(); model.reset(); } @Test public void mutationNotAllowedAfterModelIsAdded_bindListener() { thrown.expect(ImmutableModelException.class); thrown.expectMessage("Epoxy attribute fields on a model cannot be changed"); final Model_ model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.requestModelBuild(); model.onBind(null); } @Test public void mutationNotAllowedAfterModelIsAdded_unbindListener() { thrown.expect(ImmutableModelException.class); thrown.expectMessage("Epoxy attribute fields on a model cannot be changed"); final Model_ model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.requestModelBuild(); model.onUnbind(null); } @Test public void mutationNotAllowedAfterModelIsAdded_setter() { thrown.expect(ImmutableModelException.class); thrown.expectMessage("Epoxy attribute fields on a model cannot be changed"); final Model_ model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.requestModelBuild(); model.value(2); } @Test public void mutationNotAllowedAfterModelIsAdded_layout() { thrown.expect(ImmutableModelException.class); thrown.expectMessage("Epoxy attribute fields on a model cannot be changed"); final Model model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.requestModelBuild(); model.layout(R.layout.view_holder_empty_view); } @Test public void mutationNotAllowedAfterModelIsAdded_hide() { thrown.expect(ImmutableModelException.class); thrown.expectMessage("Epoxy attribute fields on a model cannot be changed"); final Model model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.requestModelBuild(); model.hide(); } @Test public void mutationAllowedDuringInterceptorCall() { final Model model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.addInterceptor(new Interceptor() { @Override public void intercept(List<EpoxyModel<?>> models) { model.reset(); } }); controller.requestModelBuild(); } @Test public void hashChangeThrows_beforeBind() { thrown.expect(ImmutableModelException.class); thrown.expectMessage("Epoxy attribute fields on a model cannot be changed"); final Model model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.requestModelBuild(); model.value = 3; new ControllerLifecycleHelper().bindModels(controller); } @Test public void hashChangeThrows_duringBind() { thrown.expect(ImmutableModelException.class); thrown.expectMessage("Epoxy attribute fields on a model cannot be changed"); final ModelChangesDuringBind_ model = new ModelChangesDuringBind_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; new ControllerLifecycleHelper().buildModelsAndBind(controller); } @Test public void hashChangeThrows_beforeNextModelBuild() { // This only works for controllers with AutoModels since only those have generated helpers thrown.expect(ImmutableModelException.class); thrown.expectMessage("Epoxy attribute fields on a model cannot be changed"); ControllerWithAutoModel controller = new ControllerWithAutoModel(); controller.requestModelBuild(); Model model = (Model) controller.getAdapter().getCopyOfModels().get(0); model.value = 3; controller.requestModelBuild(); } @Test public void hashChangeDuringInterceptorIsAllowed() { final Model_ model = new Model_().id(1); EpoxyController controller = new EpoxyController() { @Override protected void buildModels() { add(model); } }; controller.addInterceptor(new Interceptor() { @Override public void intercept(List<EpoxyModel<?>> models) { model.value(3); } }); controller.requestModelBuild(); } }