/* * 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.ArrayList; import java.util.List; import com.facebook.litho.annotations.ResType; import com.facebook.litho.specmodels.internal.ImmutableList; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Tests {@link PropValidation} */ public class PropValidationTest { private final SpecModel mSpecModel = mock(SpecModel.class); private final PropModel mPropModel1 = mock(PropModel.class); private final PropModel mPropModel2 = mock(PropModel.class); private final Object mRepresentedObject1 = new Object(); private final Object mRepresentedObject2 = new Object(); @Before public void setup() { when(mPropModel1.getName()).thenReturn("name1"); when(mPropModel2.getName()).thenReturn("name2"); when(mPropModel1.getType()).thenReturn(TypeName.BOOLEAN); when(mPropModel2.getType()).thenReturn(TypeName.INT); when(mPropModel1.isOptional()).thenReturn(false); when(mPropModel2.isOptional()).thenReturn(false); when(mPropModel1.getResType()).thenReturn(ResType.NONE); when(mPropModel2.getResType()).thenReturn(ResType.NONE); when(mPropModel1.getRepresentedObject()).thenReturn(mRepresentedObject1); when(mPropModel2.getRepresentedObject()).thenReturn(mRepresentedObject2); when(mSpecModel.getProps()).thenReturn(ImmutableList.of(mPropModel1, mPropModel2)); when(mSpecModel.getPropDefaults()).thenReturn(ImmutableList.<PropDefaultModel>of()); } @Test public void testTwoPropsWithSameNameButDifferentType() { when(mPropModel1.getName()).thenReturn("sameName"); when(mPropModel2.getName()).thenReturn("sameName"); when(mPropModel1.getType()).thenReturn(TypeName.BOOLEAN); when(mPropModel2.getType()).thenReturn(TypeName.INT); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject1); assertThat(validationErrors.get(0).message).isEqualTo( "The prop sameName is defined differently in different methods. Ensure that each " + "instance of this prop is declared in the same way (this means having the same type, " + "resType and value for isOptional)."); } @Test public void testTwoPropsWithSameNameButDifferentIsOptional() { when(mPropModel1.getName()).thenReturn("sameName"); when(mPropModel2.getName()).thenReturn("sameName"); when(mPropModel1.getType()).thenReturn(TypeName.INT); when(mPropModel2.getType()).thenReturn(TypeName.INT); when(mPropModel1.isOptional()).thenReturn(true); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject1); assertThat(validationErrors.get(0).message).isEqualTo( "The prop sameName is defined differently in different methods. Ensure that each " + "instance of this prop is declared in the same way (this means having the same type, " + "resType and value for isOptional)."); } @Test public void testTwoPropsWithSameNameButDifferentResType() { when(mPropModel1.getName()).thenReturn("sameName"); when(mPropModel2.getName()).thenReturn("sameName"); when(mPropModel1.getType()).thenReturn(TypeName.INT); when(mPropModel2.getType()).thenReturn(TypeName.INT); when(mPropModel1.getResType()).thenReturn(ResType.INT); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject1); assertThat(validationErrors.get(0).message).isEqualTo( "The prop sameName is defined differently in different methods. Ensure that each " + "instance of this prop is declared in the same way (this means having the same type, " + "resType and value for isOptional)."); } @Test public void testPropWithReservedName() { when(mPropModel1.getName()).thenReturn("withLayout"); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject1); assertThat(validationErrors.get(0).message).isEqualTo( "'withLayout' is a reserved prop name used by the component's layout builder. Please use " + "another name."); } @Test public void testPropWithReservedType() { when(mPropModel1.getType()).thenReturn(ClassNames.COMPONENT_LAYOUT); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject1); assertThat(validationErrors.get(0).message).isEqualTo( "Props may not be declared with the following types: " + "[com.facebook.litho.ComponentLayout, " + "com.facebook.litho.ComponentLayout.Builder, " + "com.facebook.litho.ComponentLayout.ContainerBuilder, " + "com.facebook.litho.Component.Builder, " + "com.facebook.litho.Component.BuilderWithLayout, " + "com.facebook.litho.reference.Reference.Builder]."); } @Test public void testOptionalPropWithDefault() { when(mPropModel1.isOptional()).thenReturn(false); when(mPropModel1.hasDefault(any(ImmutableList.class))).thenReturn(true); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject1); assertThat(validationErrors.get(0).message).isEqualTo( "name1 is not optional so it should not be declared with a default value."); } @Test public void testIncorrectTypeForResType() { when(mPropModel1.getResType()).thenReturn(ResType.BOOL); when(mPropModel1.getType()).thenReturn(TypeName.INT); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject1); assertThat(validationErrors.get(0).message).isEqualTo( "A prop declared with resType BOOL must be one of the following types: " + "[boolean, java.lang.Boolean]."); } @Test public void testDefaultDefinedWithNoCorrespondingProp() { Object propDefaultObject1 = new Object(); Object propDefaultObject2 = new Object(); PropDefaultModel propDefault1 = new PropDefaultModel( TypeName.CHAR, "name1", ImmutableList.<Modifier>of(), propDefaultObject1); PropDefaultModel propDefault2 = new PropDefaultModel( TypeName.CHAR, "notAPropName", ImmutableList.<Modifier>of(), propDefaultObject2); when(mSpecModel.getPropDefaults()).thenReturn(ImmutableList.of(propDefault1, propDefault2)); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(2); assertThat(validationErrors.get(0).element).isEqualTo(propDefaultObject1); assertThat(validationErrors.get(0).message).isEqualTo( "PropDefault name1 of type char should be of type boolean"); assertThat(validationErrors.get(1).element).isEqualTo(propDefaultObject2); assertThat(validationErrors.get(1).message).isEqualTo( "PropDefault notAPropName of type char does not correspond to any defined prop"); } @Test public void testVarArgPropMustHaveListType() { when(mPropModel1.getResType()).thenReturn(ResType.NONE); when(mPropModel1.getVarArgsSingleName()).thenReturn("test"); when(mPropModel1.hasVarArgs()).thenReturn(true); when(mPropModel1.getType()).thenReturn(TypeName.get(String.class)); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject1); assertThat(validationErrors.get(0).message).isEqualTo( "name1 is a variable argument, and thus requires a parameterized List type."); } @Test public void testVarArgPropMustHaveParameterizedListType() { when(mPropModel1.getResType()).thenReturn(ResType.NONE); when(mPropModel1.getVarArgsSingleName()).thenReturn("test"); when(mPropModel1.hasVarArgs()).thenReturn(true); when(mPropModel1.getType()) .thenReturn(ParameterizedTypeName.get(ArrayList.class, String.class)); List<SpecModelValidationError> validationErrors = PropValidation.validate(mSpecModel); assertThat(validationErrors).hasSize(1); assertThat(validationErrors.get(0).element).isEqualTo(mRepresentedObject1); assertThat(validationErrors.get(0).message).isEqualTo( "name1 is a variable argument, and thus should be a List<> type."); } }