/* * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.litho.specmodels.model; import javax.lang.model.element.Modifier; import java.util.List; import com.facebook.litho.annotations.Param; import com.facebook.litho.specmodels.internal.ImmutableList; import com.facebook.litho.testing.specmodels.TestMethodParamModel; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeVariableName; import com.squareup.javapoet.WildcardTypeName; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Tests {@link StateValidation} */ public class StateValidationTest { private final SpecModel mSpecModel = mock(SpecModel.class); private final PropModel mPropModel = mock(PropModel.class); private final StateParamModel mStateParamModel = mock(StateParamModel.class); private final Object mRepresentedObject1 = new Object(); private final Object mRepresentedObject2 = new Object(); private final Object mRepresentedObject3 = new Object(); private final Object mRepresentedObject4 = new Object(); private final Object mRepresentedObject5 = new Object(); @Before public void setup() { when(mSpecModel.getProps()).thenReturn(ImmutableList.<PropModel>of()); when(mSpecModel.getStateValues()).thenReturn(ImmutableList.<StateParamModel>of()); } @Test public void testTwoStateValuesWithSameNameButDifferentType() { StateParamModel stateValue1 = mock(StateParamModel.class); StateParamModel stateValue2 = mock(StateParamModel.class); when(stateValue1.getName()).thenReturn("sameName"); when(stateValue2.getName()).thenReturn("sameName"); when(stateValue1.getType()).thenReturn(TypeName.BOOLEAN); when(stateValue2.getType()).thenReturn(TypeName.INT); when(stateValue2.getRepresentedObject()).thenReturn(mRepresentedObject2); when(mSpecModel.getStateValues()).thenReturn(ImmutableList.of(stateValue1, stateValue2)); List<SpecModelValidationError> validationErrors = StateValidation.validateStateValues(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject2); assertThat(validationErrors.get(0).message).isEqualTo( "State values with the same name must have the same type."); } @Test public void testTwoStateValuesWithSameNameButDifferentCanUpdateLazily() { StateParamModel stateValue1 = mock(StateParamModel.class); StateParamModel stateValue2 = mock(StateParamModel.class); when(stateValue1.getName()).thenReturn("sameName"); when(stateValue2.getName()).thenReturn("sameName"); when(stateValue1.getType()).thenReturn(TypeName.BOOLEAN); when(stateValue2.getType()).thenReturn(TypeName.BOOLEAN); when(stateValue1.canUpdateLazily()).thenReturn(false); when(stateValue1.canUpdateLazily()).thenReturn(true); when(stateValue2.getRepresentedObject()).thenReturn(mRepresentedObject2); when(mSpecModel.getStateValues()).thenReturn(ImmutableList.of(stateValue1, stateValue2)); List<SpecModelValidationError> validationErrors = StateValidation.validateStateValues(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject2); assertThat(validationErrors.get(0).message).isEqualTo( "State values with the same name must have the same annotated value for " + "canUpdateLazily()."); } @Test public void testOnUpdateStateParamSameNameAsPropAndState() { MethodParamModel methodParamModel1 = TestMethodParamModel.newBuilder() .name("propName") .annotation(Param.class) .representedObject(mRepresentedObject1) .build(); MethodParamModel methodParamModel2 = TestMethodParamModel.newBuilder() .name("stateName") .annotation(Param.class) .representedObject(mRepresentedObject2) .build(); UpdateStateMethodModel updateStateMethodModel = new UpdateStateMethodModel( null, ImmutableList.of(Modifier.STATIC), null, null, ImmutableList.of(methodParamModel1, methodParamModel2), mRepresentedObject3); when(mPropModel.getName()).thenReturn("propName"); when(mStateParamModel.getName()).thenReturn("stateName"); when(mSpecModel.getProps()).thenReturn(ImmutableList.of(mPropModel)); when(mSpecModel.getStateValues()).thenReturn(ImmutableList.of(mStateParamModel)); List<SpecModelValidationError> validationErrors = StateValidation.validateOnUpdateStateMethod(mSpecModel, updateStateMethodModel); assertThat(validationErrors).hasSize(2); assertThat(validationErrors.get(0).element).isSameAs(mRepresentedObject1); assertThat(validationErrors.get(0).message) .isEqualTo("Parameters annotated with @Param should not have the same name as a @Prop."); assertThat(validationErrors.get(1).element).isSameAs(mRepresentedObject2); assertThat(validationErrors.get(1).message) .isEqualTo("Parameters annotated with @Param should not have the same name as a @State " + "value."); } @Test public void testOnUpdateStateStateParamsNotValid() { MethodParamModel methodParamModel1 = TestMethodParamModel.newBuilder() .type(TypeName.INT) .name("name1") .representedObject(mRepresentedObject1) .build(); MethodParamModel methodParamModel2 = TestMethodParamModel.newBuilder() .type(ParameterizedTypeName.get( ClassName.bestGuess("com.facebook.litho.Output"), TypeVariableName.get("T"))) .name("name2") .representedObject(mRepresentedObject2) .build(); MethodParamModel methodParamModel3 = TestMethodParamModel.newBuilder() .type(ParameterizedTypeName.get( ClassNames.STATE_VALUE, TypeVariableName.get("S"), TypeVariableName.get("T"))) .name("name3") .representedObject(mRepresentedObject3) .build(); MethodParamModel methodParamModel4 = TestMethodParamModel.newBuilder() .type(ParameterizedTypeName.get( ClassNames.STATE_VALUE, WildcardTypeName.subtypeOf(ClassName.bestGuess("java.lang.Object")))) .name("name4") .representedObject(mRepresentedObject4) .build(); UpdateStateMethodModel updateStateMethodModel = new UpdateStateMethodModel( null, ImmutableList.of(Modifier.STATIC), "methodName", null, ImmutableList.of( methodParamModel1, methodParamModel2, methodParamModel3, methodParamModel4), mRepresentedObject5); List<SpecModelValidationError> validationErrors = StateValidation.validateOnUpdateStateMethod(mSpecModel, updateStateMethodModel); assertThat(validationErrors).hasSize(4); assertThat(validationErrors.get(0).element).isSameAs(mRepresentedObject1); assertThat(validationErrors.get(0).message) .isEqualTo("Only state parameters and parameters annotated with @Param are permitted in " + "@OnUpdateState method, and all state parameters must be of type " + "com.facebook.litho.StateValue, but name1 is of type int."); assertThat(validationErrors.get(1).element).isSameAs(mRepresentedObject2); assertThat(validationErrors.get(1).message) .isEqualTo("Only state parameters and parameters annotated with @Param are permitted in " + "@OnUpdateState method, and all state parameters must be of type " + "com.facebook.litho.StateValue, but name2 is of " + "type com.facebook.litho.Output<T>."); assertThat(validationErrors.get(2).element).isSameAs(mRepresentedObject3); assertThat(validationErrors.get(2).message) .isEqualTo("All parameters of type com.facebook.litho.StateValue must define a type " + "argument, name3 in method methodName does not."); assertThat(validationErrors.get(3).element).isSameAs(mRepresentedObject4); assertThat(validationErrors.get(3).message) .isEqualTo("All parameters of type com.facebook.litho.StateValue must define a type " + "argument, name4 in method methodName does not."); } @Test public void testOnUpdateStateStateParamsNotDefinedElsewhere() { MethodParamModel methodParamModel4 = TestMethodParamModel.newBuilder() .type(ParameterizedTypeName.get( ClassNames.STATE_VALUE, ClassName.bestGuess("java.lang.Object"))) .name("name") .representedObject(mRepresentedObject1) .build(); UpdateStateMethodModel updateStateMethodModel = new UpdateStateMethodModel( null, ImmutableList.of(Modifier.STATIC), "methodName", null, ImmutableList.of(methodParamModel4), mRepresentedObject2); List<SpecModelValidationError> validationErrors = StateValidation.validateOnUpdateStateMethod(mSpecModel, updateStateMethodModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isSameAs(mRepresentedObject1); assertThat(validationErrors.get(0).message) .isEqualTo("Names of parameters of type StateValue must match the name and type of a " + "parameter annotated with @State."); } @Test public void testOnUpdateStateNotStatic() { UpdateStateMethodModel updateStateMethodModel = new UpdateStateMethodModel( null, ImmutableList.<Modifier>of(), "methodName", null, ImmutableList.<MethodParamModel>of(), mRepresentedObject1); List<SpecModelValidationError> validationErrors = StateValidation.validateOnUpdateStateMethod(mSpecModel, updateStateMethodModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isSameAs(mRepresentedObject1); assertThat(validationErrors.get(0).message) .isEqualTo("Methods in a spec that doesn't have dependency injection must be static."); } }