| // Copyright 2018 The Feed Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.android.libraries.feed.piet; |
| |
| import static com.google.android.libraries.feed.common.testing.RunnableSubject.assertThatRunnable; |
| import static com.google.android.libraries.feed.piet.StyleProvider.DIMENSION_NOT_SET; |
| import static com.google.common.truth.Truth.assertThat; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.ArgumentMatchers.same; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| import static org.mockito.Mockito.when; |
| import static org.mockito.MockitoAnnotations.initMocks; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.view.Gravity; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewGroup.LayoutParams; |
| import android.view.ViewGroup.MarginLayoutParams; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| import android.widget.FrameLayout; |
| import com.google.android.libraries.feed.common.functional.Suppliers; |
| import com.google.android.libraries.feed.common.time.testing.FakeClock; |
| import com.google.android.libraries.feed.piet.AdapterFactory.SingletonKeySupplier; |
| import com.google.android.libraries.feed.piet.host.ActionHandler; |
| import com.google.android.libraries.feed.piet.host.ActionHandler.ActionType; |
| import com.google.android.libraries.feed.piet.host.LogDataCallback; |
| import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache; |
| import com.google.android.libraries.feed.piet.ui.RoundedCornerWrapperView; |
| import com.google.android.libraries.feed.testing.shadows.ExtendedShadowView; |
| import com.google.search.now.ui.piet.AccessibilityProto.Accessibility; |
| import com.google.search.now.ui.piet.AccessibilityProto.AccessibilityRole; |
| import com.google.search.now.ui.piet.ActionsProto.Action; |
| import com.google.search.now.ui.piet.ActionsProto.Actions; |
| import com.google.search.now.ui.piet.ActionsProto.VisibilityAction; |
| import com.google.search.now.ui.piet.BindingRefsProto.ActionsBindingRef; |
| import com.google.search.now.ui.piet.BindingRefsProto.ElementBindingRef; |
| import com.google.search.now.ui.piet.BindingRefsProto.LogDataBindingRef; |
| import com.google.search.now.ui.piet.BindingRefsProto.ParameterizedTextBindingRef; |
| import com.google.search.now.ui.piet.BindingRefsProto.VisibilityBindingRef; |
| import com.google.search.now.ui.piet.ElementsProto.BindingValue; |
| import com.google.search.now.ui.piet.ElementsProto.Content; |
| import com.google.search.now.ui.piet.ElementsProto.Element; |
| import com.google.search.now.ui.piet.ElementsProto.ElementList; |
| import com.google.search.now.ui.piet.ElementsProto.ElementStack; |
| import com.google.search.now.ui.piet.ElementsProto.Visibility; |
| import com.google.search.now.ui.piet.ElementsProto.VisibilityState; |
| import com.google.search.now.ui.piet.LogDataProto.LogData; |
| import com.google.search.now.ui.piet.PietProto.Frame; |
| import com.google.search.now.ui.piet.RoundedCornersProto.RoundedCorners; |
| import com.google.search.now.ui.piet.StylesProto.Borders; |
| import com.google.search.now.ui.piet.StylesProto.GravityHorizontal; |
| import com.google.search.now.ui.piet.StylesProto.GravityVertical; |
| import com.google.search.now.ui.piet.StylesProto.StyleIdsStack; |
| import com.google.search.now.ui.piet.TextProto.ParameterizedText; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.robolectric.Robolectric; |
| import org.robolectric.RobolectricTestRunner; |
| import org.robolectric.annotation.Config; |
| import org.robolectric.shadow.api.Shadow; |
| |
| /** Tests of the {@link ElementAdapter}. */ |
| @RunWith(RobolectricTestRunner.class) |
| public class ElementAdapterTest { |
| private static final int WIDTH = 123; |
| private static final int HEIGHT = 456; |
| private static final boolean LEGACY_CORNERS_FLAG = false; |
| private static final boolean OUTLINE_CORNERS_FLAG = false; |
| |
| private final LogData logDataTest = LogData.newBuilder().build(); |
| |
| @Mock private FrameContext frameContext; |
| @Mock private HostProviders hostProviders; |
| @Mock private ActionHandler actionHandler; |
| @Mock private StyleProvider styleProvider; |
| |
| private Context context; |
| private AdapterParameters parameters; |
| private View view; |
| private RoundedCornerMaskCache maskCache; |
| |
| private TestElementAdapter adapter; |
| private boolean callbackBound; |
| private boolean callbackUnbound; |
| |
| @Before |
| public void setUp() { |
| initMocks(this); |
| context = Robolectric.buildActivity(Activity.class).get(); |
| LogDataCallback logDataCallback = |
| new LogDataCallback() { |
| @Override |
| public void onBind(LogData logData, View view) { |
| assertThat(logDataTest).isEqualTo(logData); |
| callbackBound = true; |
| } |
| |
| @Override |
| public void onUnbind(LogData logData, View view) { |
| assertThat(logDataTest).isEqualTo(logData); |
| callbackUnbound = true; |
| } |
| }; |
| parameters = |
| new AdapterParameters( |
| context, |
| Suppliers.of((ViewGroup) null), |
| hostProviders, |
| new FakeClock(), |
| LEGACY_CORNERS_FLAG, |
| OUTLINE_CORNERS_FLAG); |
| maskCache = parameters.roundedCornerMaskCache; |
| when(hostProviders.getLogDataCallback()).thenReturn(logDataCallback); |
| when(frameContext.makeStyleFor(any(StyleIdsStack.class))).thenReturn(styleProvider); |
| when(frameContext.getActionHandler()).thenReturn(actionHandler); |
| when(styleProvider.hasRoundedCorners()).thenReturn(false); |
| when(styleProvider.getRoundedCorners()).thenReturn(RoundedCorners.getDefaultInstance()); |
| when(styleProvider.createWrapperView( |
| context, maskCache, LEGACY_CORNERS_FLAG, OUTLINE_CORNERS_FLAG)) |
| .thenReturn(new FrameLayout(context)); |
| |
| view = new View(context); |
| adapter = new TestElementAdapter(context, parameters, view); |
| } |
| |
| @Test |
| public void testGetters() { |
| Element defaultElement = |
| Element.newBuilder() |
| .setGravityHorizontal(GravityHorizontal.GRAVITY_CENTER) |
| .build(); |
| Frame frame = Frame.newBuilder().setTag("FRAME").build(); |
| when(frameContext.getFrame()).thenReturn(frame); |
| when(styleProvider.getGravityHorizontal(Gravity.CLIP_HORIZONTAL)) |
| .thenReturn(Gravity.CENTER_HORIZONTAL); |
| |
| adapter.createAdapter(defaultElement, defaultElement, frameContext); |
| |
| assertThat(adapter.getContext()).isSameInstanceAs(context); |
| assertThat(adapter.getModel()).isSameInstanceAs(defaultElement); |
| assertThat(adapter.getRawModel()).isSameInstanceAs(defaultElement); |
| assertThat(adapter.getHorizontalGravity(Gravity.CLIP_HORIZONTAL)) |
| .isEqualTo(Gravity.CENTER_HORIZONTAL); |
| assertThat(adapter.getParameters()).isSameInstanceAs(parameters); |
| assertThat(adapter.getTemplatedStringEvaluator()) |
| .isSameInstanceAs(parameters.templatedStringEvaluator); |
| assertThat(adapter.getElementStyle()).isSameInstanceAs(styleProvider); |
| assertThat(adapter.getElementStyleIdsStack()).isEqualTo(TestElementAdapter.SUB_ELEMENT_STYLE); |
| } |
| |
| // TODO: remove Element gravity and roll this test into the above testGetters |
| @Test |
| public void getGravity() { |
| // Pre-creation |
| assertThat(adapter.getHorizontalGravity(Gravity.CLIP_HORIZONTAL)) |
| .isEqualTo(Gravity.CLIP_HORIZONTAL); |
| assertThat(adapter.getVerticalGravity(Gravity.CLIP_VERTICAL)).isEqualTo(Gravity.CLIP_VERTICAL); |
| assertThat(adapter.getGravity(Gravity.AXIS_Y_SHIFT)).isEqualTo(Gravity.AXIS_Y_SHIFT); |
| |
| when(styleProvider.hasGravityHorizontal()).thenReturn(true); |
| when(styleProvider.hasGravityVertical()).thenReturn(true); |
| when(styleProvider.getGravityHorizontal(anyInt())).thenReturn(Gravity.CENTER_HORIZONTAL); |
| when(styleProvider.getGravityVertical(anyInt())).thenReturn(Gravity.CENTER_VERTICAL); |
| // This should typically be CENTER_HORIZONTAL | CENTER_VERTICAL but I want to make sure that |
| // ElementAdapter is calling this method instead of OR'ing the H/V gravities itself. |
| when(styleProvider.getGravity(anyInt())).thenReturn(Gravity.FILL); |
| |
| Element defaultElement = |
| Element.newBuilder() |
| .setGravityHorizontal(GravityHorizontal.GRAVITY_CENTER) |
| .setGravityVertical(GravityVertical.GRAVITY_MIDDLE) |
| .build(); |
| |
| adapter.createAdapter(defaultElement, defaultElement, frameContext); |
| |
| assertThat(adapter.getHorizontalGravity(Gravity.CLIP_VERTICAL)) |
| .isEqualTo(Gravity.CENTER_HORIZONTAL); |
| assertThat(adapter.getVerticalGravity(Gravity.CLIP_HORIZONTAL)) |
| .isEqualTo(Gravity.CENTER_VERTICAL); |
| assertThat(adapter.getGravity(Gravity.CLIP_VERTICAL)).isEqualTo(Gravity.FILL); |
| } |
| |
| @Test |
| public void setLayoutParams() { |
| adapter.setLayoutParams(new LayoutParams(WIDTH, HEIGHT)); |
| |
| assertThat(view.getLayoutParams().width).isEqualTo(WIDTH); |
| assertThat(view.getLayoutParams().height).isEqualTo(HEIGHT); |
| } |
| |
| @Test |
| public void setLayoutParams_overlay() { |
| Element elementWithOverlays = |
| Element.newBuilder() |
| .addOverlays( |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder().setElementList(ElementList.getDefaultInstance()))) |
| .build(); |
| |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| |
| adapter.setLayoutParams(new LayoutParams(WIDTH, HEIGHT)); |
| |
| assertThat(adapter.getBaseView()).isNotEqualTo(adapter.getView()); |
| assertThat(adapter.getView().getLayoutParams().width).isEqualTo(WIDTH); |
| assertThat(adapter.getView().getLayoutParams().height).isEqualTo(HEIGHT); |
| assertThat(adapter.getBaseView().getLayoutParams().width).isEqualTo(LayoutParams.MATCH_PARENT); |
| assertThat(adapter.getBaseView().getLayoutParams().height).isEqualTo(LayoutParams.MATCH_PARENT); |
| |
| adapter.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); |
| |
| assertThat(adapter.getBaseView()).isNotEqualTo(adapter.getView()); |
| assertThat(adapter.getView().getLayoutParams().width).isEqualTo(LayoutParams.WRAP_CONTENT); |
| assertThat(adapter.getView().getLayoutParams().height).isEqualTo(LayoutParams.WRAP_CONTENT); |
| assertThat(adapter.getBaseView().getLayoutParams().width).isEqualTo(LayoutParams.WRAP_CONTENT); |
| assertThat(adapter.getBaseView().getLayoutParams().height).isEqualTo(LayoutParams.WRAP_CONTENT); |
| } |
| |
| @Test |
| public void setLayoutParams_resetsMarginsWithOverlay() { |
| Element elementWithOverlays = |
| Element.newBuilder() |
| .addOverlays( |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder().setElementList(ElementList.getDefaultInstance()))) |
| .build(); |
| |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| |
| MarginLayoutParams paramsWithMargins = new MarginLayoutParams(123, 456); |
| paramsWithMargins.setMargins(1, 2, 3, 4); |
| adapter.getBaseView().setLayoutParams(paramsWithMargins); |
| |
| adapter.setLayoutParams(new LayoutParams(WIDTH, HEIGHT)); |
| |
| assertThat(((MarginLayoutParams) adapter.getBaseView().getLayoutParams()).leftMargin) |
| .isEqualTo(0); |
| assertThat(((MarginLayoutParams) adapter.getBaseView().getLayoutParams()).rightMargin) |
| .isEqualTo(0); |
| assertThat(((MarginLayoutParams) adapter.getBaseView().getLayoutParams()).topMargin) |
| .isEqualTo(0); |
| assertThat(((MarginLayoutParams) adapter.getBaseView().getLayoutParams()).bottomMargin) |
| .isEqualTo(0); |
| } |
| |
| @Test |
| public void getComputedDimensions_defaults() { |
| assertThat(adapter.getComputedWidthPx()).isEqualTo(DIMENSION_NOT_SET); |
| assertThat(adapter.getComputedHeightPx()).isEqualTo(DIMENSION_NOT_SET); |
| } |
| |
| @Test |
| public void getComputedDimensions_explicit() { |
| adapter.setDims(WIDTH, HEIGHT); |
| |
| assertThat(adapter.getComputedWidthPx()).isEqualTo(WIDTH); |
| assertThat(adapter.getComputedHeightPx()).isEqualTo(HEIGHT); |
| } |
| |
| @Test |
| public void createAdapter_callsOnCreateAdapter() { |
| Element defaultElement = Element.getDefaultInstance(); |
| |
| adapter.createAdapter(defaultElement, defaultElement, frameContext); |
| |
| assertThat(adapter.testAdapterCreated).isTrue(); |
| } |
| |
| @Test |
| public void createAdapter_extractsModelFromElement() { |
| Element element = Element.getDefaultInstance(); |
| |
| adapter.createAdapter(element, frameContext); |
| |
| assertThat(adapter.getModel()).isEqualTo(TestElementAdapter.DEFAULT_MODEL); |
| } |
| |
| @Test |
| public void createAdapter_createsOverlays() { |
| Element elementWithOverlays = |
| Element.newBuilder() |
| .addOverlays( |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder().setElementList(ElementList.getDefaultInstance()))) |
| .build(); |
| |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| |
| assertThat(adapter.getView()).isNotEqualTo(adapter.getBaseView()); |
| assertThat(adapter.getView()).isInstanceOf(FrameLayout.class); |
| assertThat(adapter.overlayAdapter.childAdapters).isNotEmpty(); |
| } |
| |
| @Test |
| public void createAdapter_setsOverlayLayoutParams() { |
| Content overlayContent = |
| Content.newBuilder() |
| .setElement(Element.newBuilder().setElementList(ElementList.getDefaultInstance())) |
| .build(); |
| Element elementWithOverlays = Element.newBuilder().addOverlays(overlayContent).build(); |
| |
| ElementAdapterFactory mockFactory = mock(ElementAdapterFactory.class); |
| ElementStackAdapter mockOverlayAdapter = mock(ElementStackAdapter.class); |
| View overlayView = new View(context); |
| when(mockFactory.createOverlayAdapter(elementWithOverlays.getOverlaysList(), frameContext)) |
| .thenReturn(mockOverlayAdapter); |
| when(mockOverlayAdapter.getView()).thenReturn(overlayView); |
| when(mockOverlayAdapter.getElementStyle()).thenReturn(styleProvider); |
| |
| parameters = |
| new AdapterParameters( |
| context, |
| Suppliers.of((ViewGroup) null), |
| hostProviders, |
| new ParameterizedTextEvaluator(new FakeClock()), |
| mockFactory, |
| mock(TemplateBinder.class), |
| new FakeClock()); |
| when(styleProvider.createWrapperView( |
| context, |
| parameters.roundedCornerMaskCache, |
| parameters.allowLegacyRoundedCornerImpl, |
| parameters.allowOutlineRoundedCornerImpl)) |
| .thenReturn(new FrameLayout(context)); |
| adapter = new TestElementAdapter(context, parameters, view); |
| |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| |
| assertThat(((FrameLayout) adapter.getView()).getChildAt(1)).isSameInstanceAs(overlayView); |
| ArgumentCaptor<LayoutParams> layoutParamsCaptor = ArgumentCaptor.forClass(LayoutParams.class); |
| verify(mockOverlayAdapter).setLayoutParams(layoutParamsCaptor.capture()); |
| assertThat(layoutParamsCaptor.getValue().width).isEqualTo(LayoutParams.MATCH_PARENT); |
| assertThat(layoutParamsCaptor.getValue().height).isEqualTo(LayoutParams.MATCH_PARENT); |
| } |
| |
| @Test |
| public void createAdapter_doesNothingForBoundOverlays() { |
| ElementBindingRef bindingRef = ElementBindingRef.newBuilder().setBindingId("overlay").build(); |
| BindingValue bindingValue = |
| BindingValue.newBuilder() |
| .setElement(Element.newBuilder().setElementList(ElementList.getDefaultInstance())) |
| .build(); |
| Content overlayContent = Content.newBuilder().setBoundElement(bindingRef).build(); |
| when(frameContext.getElementBindingValue(bindingRef)).thenReturn(bindingValue); |
| Element elementWithOverlays = Element.newBuilder().addOverlays(overlayContent).build(); |
| |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| |
| // No overlays have been created yet, but adapter exists |
| assertThat(adapter.overlayAdapter.childAdapters).isEmpty(); |
| } |
| |
| @Test |
| public void createAdapter_addsBorders() { |
| when(styleProvider.hasBorders()).thenReturn(true); |
| |
| adapter.createAdapter(Element.getDefaultInstance(), Element.getDefaultInstance(), frameContext); |
| |
| verify(styleProvider).addBordersWithoutRoundedCorners((FrameLayout) adapter.getView(), context); |
| } |
| |
| @Test |
| public void createAdapter_appliesVisibility() { |
| Element defaultElement = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder().setDefaultVisibility(Visibility.INVISIBLE)) |
| .build(); |
| |
| adapter.createAdapter(defaultElement, frameContext); |
| |
| assertThat(adapter.getView().getVisibility()).isEqualTo(View.INVISIBLE); |
| } |
| |
| @Test |
| public void createAdapter_doesNothingWithVisibilityGone() { |
| Element defaultElement = |
| Element.newBuilder() |
| .setVisibilityState(VisibilityState.newBuilder().setDefaultVisibility(Visibility.GONE)) |
| .build(); |
| |
| adapter.createAdapter(defaultElement, frameContext); |
| |
| assertThat(adapter.testAdapterCreated).isFalse(); |
| assertThat(adapter.getView().getVisibility()).isEqualTo(View.GONE); |
| } |
| |
| @Test |
| public void createAdapter_ignoresBoundVisibility() { |
| String visibilityBindingId = "invisible"; |
| VisibilityBindingRef visibilityBinding = |
| VisibilityBindingRef.newBuilder().setBindingId(visibilityBindingId).build(); |
| Element defaultElement = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder() |
| .setDefaultVisibility(Visibility.VISIBLE) |
| .setOverridingBoundVisibility(visibilityBinding)) |
| .build(); |
| when(frameContext.getVisibilityFromBinding(visibilityBinding)).thenReturn(Visibility.GONE); |
| |
| adapter.createAdapter(defaultElement, frameContext); |
| |
| assertThat(adapter.testAdapterCreated).isTrue(); |
| assertThat(adapter.getView().getVisibility()).isEqualTo(View.VISIBLE); |
| } |
| |
| @Test |
| public void createAdapter_setsDimensions() { |
| int width = 10; |
| int height = 20; |
| |
| when(styleProvider.hasWidth()).thenReturn(true); |
| when(styleProvider.hasHeight()).thenReturn(true); |
| when(styleProvider.getWidthSpecPx(context)).thenReturn(width); |
| when(styleProvider.getHeightSpecPx(context)).thenReturn(height); |
| |
| adapter.createAdapter(Element.getDefaultInstance(), frameContext); |
| |
| assertThat(adapter.getComputedWidthPx()).isEqualTo(width); |
| assertThat(adapter.getComputedHeightPx()).isEqualTo(height); |
| } |
| |
| @Test |
| public void createAdapter_resetsUnsetDimensions() { |
| // Set some dimensions first |
| int width = 10; |
| int height = 20; |
| |
| when(styleProvider.hasWidth()).thenReturn(true); |
| when(styleProvider.hasHeight()).thenReturn(true); |
| when(styleProvider.getWidthSpecPx(context)).thenReturn(width); |
| when(styleProvider.getHeightSpecPx(context)).thenReturn(height); |
| |
| adapter.createAdapter(Element.getDefaultInstance(), frameContext); |
| adapter.releaseAdapter(); |
| |
| // Recreate with new style that unsets the dimensions |
| when(styleProvider.hasWidth()).thenReturn(false); |
| when(styleProvider.hasHeight()).thenReturn(false); |
| when(styleProvider.getWidthSpecPx(context)).thenReturn(DIMENSION_NOT_SET); |
| when(styleProvider.getHeightSpecPx(context)).thenReturn(DIMENSION_NOT_SET); |
| |
| adapter.createAdapter(Element.getDefaultInstance(), frameContext); |
| |
| assertThat(adapter.getComputedWidthPx()).isEqualTo(DIMENSION_NOT_SET); |
| assertThat(adapter.getComputedHeightPx()).isEqualTo(DIMENSION_NOT_SET); |
| } |
| |
| @Test |
| public void createAdapter_dimensionsDontOverrideSubclass() { |
| int width = 10; |
| int height = 20; |
| |
| final int subclassWidth = 30; |
| final int subclassHeight = 40; |
| |
| adapter = |
| new TestElementAdapter(context, parameters, view) { |
| @Override |
| protected void onCreateAdapter( |
| Object model, Element baseElement, FrameContext frameContext) { |
| widthPx = subclassWidth; |
| heightPx = subclassHeight; |
| } |
| }; |
| |
| when(styleProvider.hasWidth()).thenReturn(true); |
| when(styleProvider.hasHeight()).thenReturn(true); |
| when(styleProvider.getWidthSpecPx(context)).thenReturn(width); |
| when(styleProvider.getHeightSpecPx(context)).thenReturn(height); |
| |
| adapter.createAdapter(Element.getDefaultInstance(), frameContext); |
| |
| assertThat(adapter.getComputedWidthPx()).isEqualTo(subclassWidth); |
| assertThat(adapter.getComputedHeightPx()).isEqualTo(subclassHeight); |
| } |
| |
| @Test |
| public void bindModel_callsOnBindModel() { |
| Element defaultElement = Element.getDefaultInstance(); |
| |
| adapter.bindModel(defaultElement, defaultElement, frameContext); |
| |
| assertThat(adapter.testAdapterBound).isTrue(); |
| } |
| |
| @Test |
| public void testBindModel_callsOnBindLogDataCallback() { |
| Element defaultElement = Element.newBuilder().setLogData(logDataTest).build(); |
| |
| adapter.bindModel(defaultElement, defaultElement, frameContext); |
| |
| assertThat(callbackBound).isTrue(); |
| } |
| |
| @Test |
| public void testBindModel_callsOnBindLogDataCallback_bindingRef() { |
| LogDataBindingRef logDataBinding = |
| LogDataBindingRef.newBuilder().setBindingId("LogData!").build(); |
| Element defaultElement = Element.newBuilder().setLogDataRef(logDataBinding).build(); |
| when(frameContext.getLogDataFromBinding(logDataBinding)).thenReturn(logDataTest); |
| adapter.bindModel(defaultElement, defaultElement, frameContext); |
| |
| assertThat(callbackBound).isTrue(); |
| } |
| |
| @Test |
| public void bindModel_extractsModelFromElement() { |
| Element element = Element.getDefaultInstance(); |
| |
| adapter.bindModel(element, frameContext); |
| |
| assertThat(adapter.getModel()).isEqualTo(TestElementAdapter.DEFAULT_MODEL); |
| } |
| |
| @Test |
| public void bindModel_bindsOverlays() { |
| Content overlayContent = |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder() |
| .setGravityVertical(GravityVertical.GRAVITY_MIDDLE) |
| .setElementList(ElementList.getDefaultInstance())) |
| .build(); |
| Element elementWithOverlays = Element.newBuilder().addOverlays(overlayContent).build(); |
| |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| assertThat(adapter.overlayAdapter).isNotNull(); |
| |
| ElementStackAdapter mockOverlayAdapter = mock(ElementStackAdapter.class); |
| adapter.overlayAdapter = mockOverlayAdapter; |
| |
| adapter.bindModel(elementWithOverlays, elementWithOverlays, frameContext); |
| verify(mockOverlayAdapter) |
| .bindModel( |
| Element.newBuilder() |
| .setElementStack( |
| ElementStack.newBuilder().addAllContents(elementWithOverlays.getOverlaysList())) |
| .build(), |
| frameContext); |
| } |
| |
| @Test |
| public void bindModel_bindsBoundOverlays() { |
| ElementBindingRef bindingRef = ElementBindingRef.newBuilder().setBindingId("overlay").build(); |
| BindingValue bindingValue = |
| BindingValue.newBuilder() |
| .setElement(Element.newBuilder().setElementList(ElementList.getDefaultInstance())) |
| .build(); |
| Content overlayContent = Content.newBuilder().setBoundElement(bindingRef).build(); |
| when(frameContext.getElementBindingValue(bindingRef)).thenReturn(bindingValue); |
| Element elementWithOverlays = Element.newBuilder().addOverlays(overlayContent).build(); |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| |
| adapter.bindModel(elementWithOverlays, elementWithOverlays, frameContext); |
| |
| assertThat(adapter.overlayAdapter.childAdapters.get(0)).isInstanceOf(ElementListAdapter.class); |
| } |
| |
| @Test |
| public void bindModel_setsActions() { |
| Actions actions = Actions.newBuilder().setOnClickAction(Action.getDefaultInstance()).build(); |
| Element elementWithActions = Element.newBuilder().setActions(actions).build(); |
| |
| adapter.bindModel(elementWithActions, elementWithActions, frameContext); |
| assertThat(adapter.getView().hasOnClickListeners()).isTrue(); |
| assertThat(adapter.actions).isSameInstanceAs(actions); |
| } |
| |
| @Test |
| public void bindModel_setsActionsOnBaseView() { |
| Actions actions = Actions.newBuilder().setOnClickAction(Action.getDefaultInstance()).build(); |
| Element elementWithActions = |
| Element.newBuilder() |
| .setActions(actions) |
| .addOverlays( |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder().setElementList(ElementList.getDefaultInstance()))) |
| .build(); |
| |
| adapter.createAdapter(elementWithActions, elementWithActions, frameContext); |
| adapter.bindModel(elementWithActions, elementWithActions, frameContext); |
| assertThat(adapter.getView().hasOnClickListeners()).isFalse(); |
| assertThat(adapter.getBaseView().hasOnClickListeners()).isTrue(); |
| assertThat(adapter.actions).isSameInstanceAs(actions); |
| } |
| |
| @Test |
| public void bindModel_setsActionsWithBinding() { |
| Actions actions = Actions.newBuilder().setOnClickAction(Action.getDefaultInstance()).build(); |
| ActionsBindingRef actionsBinding = |
| ActionsBindingRef.newBuilder().setBindingId("ACTION!").build(); |
| Element elementWithActions = Element.newBuilder().setActionsBinding(actionsBinding).build(); |
| when(frameContext.getActionsFromBinding(actionsBinding)).thenReturn(actions); |
| |
| adapter.bindModel(elementWithActions, elementWithActions, frameContext); |
| assertThat(adapter.getView().hasOnClickListeners()).isTrue(); |
| assertThat(adapter.actions).isSameInstanceAs(actions); |
| } |
| |
| @Test |
| public void bindModel_unsetsActions() { |
| Actions actions = Actions.newBuilder().setOnClickAction(Action.getDefaultInstance()).build(); |
| Element elementWithActions = Element.newBuilder().setActions(actions).build(); |
| Element elementWithoutActions = Element.getDefaultInstance(); |
| |
| adapter.bindModel(elementWithActions, elementWithActions, frameContext); |
| assertThat(adapter.getView().hasOnClickListeners()).isTrue(); |
| assertThat(adapter.actions).isSameInstanceAs(actions); |
| |
| adapter.bindModel(elementWithoutActions, elementWithoutActions, frameContext); |
| assertThat(adapter.getView().hasOnClickListeners()).isFalse(); |
| assertThat(adapter.actions).isSameInstanceAs(Actions.getDefaultInstance()); |
| } |
| |
| @Test |
| public void bindModel_unsetsActionsOnBaseView() { |
| Actions actions = Actions.newBuilder().setOnClickAction(Action.getDefaultInstance()).build(); |
| Element elementWithActions = |
| Element.newBuilder() |
| .setActions(actions) |
| .addOverlays( |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder().setElementList(ElementList.getDefaultInstance()))) |
| .build(); |
| Element elementWithoutActions = elementWithActions.toBuilder().clearActions().build(); |
| |
| adapter.createAdapter(elementWithActions, elementWithActions, frameContext); |
| adapter.bindModel(elementWithActions, elementWithActions, frameContext); |
| assertThat(adapter.getView().hasOnClickListeners()).isFalse(); |
| assertThat(adapter.getBaseView().hasOnClickListeners()).isTrue(); |
| assertThat(adapter.actions).isSameInstanceAs(actions); |
| |
| adapter.unbindModel(); |
| adapter.bindModel(elementWithoutActions, elementWithoutActions, frameContext); |
| assertThat(adapter.getView().hasOnClickListeners()).isFalse(); |
| assertThat(adapter.getBaseView().hasOnClickListeners()).isFalse(); |
| assertThat(adapter.actions).isSameInstanceAs(Actions.getDefaultInstance()); |
| } |
| |
| @Test |
| public void bindModel_failsWithIncompatibleOverlays() { |
| Content overlayContent = |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder() |
| .setGravityVertical(GravityVertical.GRAVITY_MIDDLE) |
| .setElementList(ElementList.getDefaultInstance())) |
| .build(); |
| Element elementWithOverlays = |
| Element.newBuilder().addOverlays(overlayContent).addOverlays(overlayContent).build(); |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| |
| Element elementWithFewerOverlays = Element.newBuilder().addOverlays(overlayContent).build(); |
| |
| assertThatRunnable( |
| () -> |
| adapter.bindModel(elementWithFewerOverlays, elementWithFewerOverlays, frameContext)) |
| .throwsAnExceptionOfType(IllegalStateException.class) |
| .that() |
| .hasMessageThat() |
| .contains("Internal error in adapters per content"); |
| } |
| |
| @Test |
| public void bindModel_setsVisibility() { |
| Element visibleElement = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder().setDefaultVisibility(Visibility.VISIBLE)) |
| .build(); |
| adapter.createAdapter(visibleElement, frameContext); |
| Element invisibleElement = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder().setDefaultVisibility(Visibility.INVISIBLE)) |
| .build(); |
| |
| adapter.bindModel(invisibleElement, frameContext); |
| |
| assertThat(adapter.testAdapterBound).isTrue(); |
| assertThat(adapter.getView().getVisibility()).isEqualTo(View.INVISIBLE); |
| } |
| |
| @Test |
| public void bindModel_doesNothingWhenVisibilityIsGone() { |
| Element visibleElement = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder().setDefaultVisibility(Visibility.VISIBLE)) |
| .build(); |
| adapter.createAdapter(visibleElement, frameContext); |
| Element goneElement = |
| Element.newBuilder() |
| .setVisibilityState(VisibilityState.newBuilder().setDefaultVisibility(Visibility.GONE)) |
| .build(); |
| |
| adapter.bindModel(goneElement, frameContext); |
| |
| assertThat(adapter.testAdapterBound).isFalse(); |
| assertThat(adapter.getView().getVisibility()).isEqualTo(View.GONE); |
| } |
| |
| @Test |
| public void bindModel_recreatesWhenVisibilityWasGone() { |
| Element goneElement = |
| Element.newBuilder() |
| .setVisibilityState(VisibilityState.newBuilder().setDefaultVisibility(Visibility.GONE)) |
| .build(); |
| adapter.createAdapter(goneElement, frameContext); |
| Element invisibleElement = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder().setDefaultVisibility(Visibility.INVISIBLE)) |
| .build(); |
| |
| adapter.bindModel(invisibleElement, frameContext); |
| |
| assertThat(adapter.testAdapterCreated).isTrue(); |
| assertThat(adapter.testAdapterBound).isTrue(); |
| assertThat(adapter.getView().getVisibility()).isEqualTo(View.INVISIBLE); |
| } |
| |
| @Test |
| public void bindModel_recreatesWhenVisibilityWasGone_bound() { |
| VisibilityBindingRef visibilityBinding = |
| VisibilityBindingRef.newBuilder().setBindingId("camo").build(); |
| Element elementWithVisibilityBinding = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder() |
| .setDefaultVisibility(Visibility.GONE) |
| .setOverridingBoundVisibility(visibilityBinding)) |
| .build(); |
| |
| // When adapter is created, the Element is GONE |
| when(frameContext.getVisibilityFromBinding(visibilityBinding)).thenReturn(Visibility.GONE); |
| |
| adapter.createAdapter(elementWithVisibilityBinding, frameContext); |
| |
| assertThat(adapter.testAdapterCreated).isFalse(); |
| assertThat(adapter.getView().getVisibility()).isEqualTo(View.GONE); |
| |
| // Now on binding, the Element is VISIBLE |
| when(frameContext.getVisibilityFromBinding(visibilityBinding)).thenReturn(Visibility.VISIBLE); |
| |
| adapter.bindModel(elementWithVisibilityBinding, frameContext); |
| |
| assertThat(adapter.testAdapterCreated).isTrue(); |
| assertThat(adapter.testAdapterBound).isTrue(); |
| assertThat(adapter.getView().getVisibility()).isEqualTo(View.VISIBLE); |
| } |
| |
| @Test |
| public void bindModel_doesntRecreateWhenWasCreatedButVisibilityGone() { |
| // Create a real adapter for a visible element. |
| Element visibileElement = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder().setDefaultVisibility(Visibility.VISIBLE)) |
| .build(); |
| adapter.createAdapter(visibileElement, frameContext); |
| |
| // Bind a gone element, and unbind it so the visibility is GONE. |
| Element goneElement = |
| Element.newBuilder() |
| .setVisibilityState(VisibilityState.newBuilder().setDefaultVisibility(Visibility.GONE)) |
| .build(); |
| adapter.bindModel(goneElement, frameContext); |
| adapter.unbindModel(); |
| |
| assertThat(adapter.testAdapterCreated).isTrue(); |
| adapter.testAdapterCreated = false; // set this so we can test that it doesn't get recreated |
| |
| // Bind a real element, and ensure that the adapter is not recreated. |
| Element invisibleElement = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder().setDefaultVisibility(Visibility.INVISIBLE)) |
| .build(); |
| |
| adapter.bindModel(invisibleElement, frameContext); |
| |
| assertThat(adapter.testAdapterCreated).isFalse(); |
| assertThat(adapter.testAdapterBound).isTrue(); |
| assertThat(adapter.getView().getVisibility()).isEqualTo(View.INVISIBLE); |
| } |
| |
| @Test |
| public void bindModel_accessibility() { |
| Element accessibilityElement = |
| Element.newBuilder() |
| .setAccessibility( |
| Accessibility.newBuilder() |
| .addRoles(AccessibilityRole.HEADER) |
| .setDescription(ParameterizedText.newBuilder().setText("Accessible!"))) |
| .build(); |
| |
| adapter.bindModel(accessibilityElement, frameContext); |
| assertThat(adapter.getView().getContentDescription().toString()).isEqualTo("Accessible!"); |
| final AccessibilityNodeInfo nodeInfo = AccessibilityNodeInfo.obtain(); |
| adapter.getView().onInitializeAccessibilityNodeInfo(nodeInfo); |
| assertThat(nodeInfo.getCollectionItemInfo().isHeading()).isTrue(); |
| } |
| |
| @Test |
| public void bindModel_accessibilityBinding() { |
| ParameterizedTextBindingRef textBinding = |
| ParameterizedTextBindingRef.newBuilder().setBindingId("binding").build(); |
| Element accessibilityElement = |
| Element.newBuilder() |
| .setAccessibility(Accessibility.newBuilder().setDescriptionBinding(textBinding)) |
| .build(); |
| when(frameContext.getParameterizedTextBindingValue(textBinding)) |
| .thenReturn( |
| BindingValue.newBuilder() |
| .setParameterizedText(ParameterizedText.newBuilder().setText("Accessible!")) |
| .build()); |
| |
| adapter.bindModel(accessibilityElement, frameContext); |
| |
| assertThat(adapter.getView().getContentDescription().toString()).isEqualTo("Accessible!"); |
| } |
| |
| @Test |
| public void bindModel_accessibilityReset() { |
| adapter.getView().setContentDescription("OLD CONTENT DESCRIPTION"); |
| |
| adapter.bindModel(Element.getDefaultInstance(), frameContext); |
| |
| assertThat(adapter.getView().getContentDescription()).isNull(); |
| } |
| |
| @Test |
| public void unbindModel_callsOnUnbindModel() { |
| Element defaultElement = Element.getDefaultInstance(); |
| |
| adapter.createAdapter(defaultElement, defaultElement, frameContext); |
| adapter.bindModel(defaultElement, defaultElement, frameContext); |
| assertThat(adapter.testAdapterBound).isTrue(); |
| |
| adapter.unbindModel(); |
| assertThat(adapter.testAdapterBound).isFalse(); |
| } |
| |
| @Test |
| public void testUnbindModel_callsOnBindLogDataCallback() { |
| Element defaultElement = Element.newBuilder().setLogData(logDataTest).build(); |
| |
| adapter.bindModel(defaultElement, defaultElement, frameContext); |
| adapter.unbindModel(); |
| assertThat(callbackUnbound).isTrue(); |
| } |
| |
| @Test |
| public void testUnbindModel_callsOnBindLogDataCallback_bindingRef() { |
| LogDataBindingRef logDataBinding = |
| LogDataBindingRef.newBuilder().setBindingId("LogData!").build(); |
| Element defaultElement = Element.newBuilder().setLogDataRef(logDataBinding).build(); |
| when(frameContext.getLogDataFromBinding(logDataBinding)).thenReturn(logDataTest); |
| |
| adapter.bindModel(defaultElement, defaultElement, frameContext); |
| adapter.unbindModel(); |
| assertThat(callbackUnbound).isTrue(); |
| } |
| |
| @Test |
| public void unbindModel_unsetsModel() { |
| Element element = Element.getDefaultInstance(); |
| |
| adapter.createAdapter(element, element, frameContext); |
| adapter.bindModel(element, element, frameContext); |
| assertThat(adapter.getModel()).isEqualTo(element); |
| |
| adapter.unbindModel(); |
| assertThat(adapter.getRawModel()).isNull(); |
| } |
| |
| // If onBindModel was never called, onUnbindModel should not be called. |
| @Test |
| public void unbindModel_visibilityWasGone() { |
| Element goneElement = |
| Element.newBuilder() |
| .setVisibilityState(VisibilityState.newBuilder().setDefaultVisibility(Visibility.GONE)) |
| .build(); |
| adapter.createAdapter(goneElement, frameContext); |
| adapter.bindModel(goneElement, frameContext); |
| |
| assertThat(adapter.testAdapterCreated).isFalse(); |
| assertThat(adapter.testAdapterBound).isFalse(); |
| |
| // Set this to something else so we can check if onUnbindModel gets called - it should not. |
| adapter.testAdapterBound = true; |
| |
| adapter.unbindModel(); |
| |
| // onUnbindModel was not called so this is still true. |
| assertThat(adapter.testAdapterBound).isTrue(); |
| assertThat(adapter.testAdapterCreated).isFalse(); |
| } |
| |
| @Test |
| public void unbindModel_unsetsActions() { |
| Element elementWithOverlaysAndActions = |
| Element.newBuilder() |
| .addOverlays( |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder() |
| .setActions( |
| Actions.newBuilder().setOnClickAction(Action.getDefaultInstance())) |
| .setElementList(ElementList.getDefaultInstance()))) |
| .setActions(Actions.newBuilder().setOnClickAction(Action.getDefaultInstance())) |
| .build(); |
| |
| adapter.createAdapter( |
| elementWithOverlaysAndActions, elementWithOverlaysAndActions, frameContext); |
| adapter.bindModel(elementWithOverlaysAndActions, elementWithOverlaysAndActions, frameContext); |
| View adapterView = adapter.getBaseView(); |
| View overlayView = adapter.getView(); |
| |
| adapter.unbindModel(); |
| |
| assertThat(adapterView.hasOnClickListeners()).isFalse(); |
| assertThat(overlayView.hasOnClickListeners()).isFalse(); |
| } |
| |
| @Test |
| public void unbindModel_unbindsOverlayAdapters() { |
| Content overlayContent = |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder() |
| .setGravityVertical(GravityVertical.GRAVITY_MIDDLE) |
| .setElementList(ElementList.getDefaultInstance())) |
| .build(); |
| Element elementWithOverlays = Element.newBuilder().addOverlays(overlayContent).build(); |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| adapter.bindModel(elementWithOverlays, elementWithOverlays, frameContext); |
| assertThat(adapter.overlayAdapter.getRawModel()).isNotNull(); |
| |
| adapter.unbindModel(); |
| assertThat(adapter.overlayAdapter.getRawModel()).isNull(); |
| } |
| |
| @Test |
| public void unbindModel_releasesBoundOverlays() { |
| Element overlayElement = |
| Element.newBuilder() |
| .setGravityVertical(GravityVertical.GRAVITY_MIDDLE) |
| .setElementList(ElementList.getDefaultInstance()) |
| .build(); |
| String overlayBindingId = "overlay"; |
| ElementBindingRef overlayBindingRef = |
| ElementBindingRef.newBuilder().setBindingId(overlayBindingId).build(); |
| BindingValue overlayBindingValue = |
| BindingValue.newBuilder().setBindingId(overlayBindingId).setElement(overlayElement).build(); |
| when(frameContext.getElementBindingValue(overlayBindingRef)).thenReturn(overlayBindingValue); |
| Content overlayContent = Content.newBuilder().setBoundElement(overlayBindingRef).build(); |
| Element elementWithOverlays = Element.newBuilder().addOverlays(overlayContent).build(); |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| assertThat(adapter.overlayAdapter).isNotNull(); |
| |
| adapter.bindModel(elementWithOverlays, elementWithOverlays, frameContext); |
| assertThat(adapter.overlayAdapter).isNotNull(); |
| ElementStackAdapter overlayAdapter = adapter.overlayAdapter; |
| assertThat(overlayAdapter.childAdapters).hasSize(1); |
| assertThat(overlayAdapter.childAdapters.get(0)).isInstanceOf(ElementListAdapter.class); |
| ElementListAdapter mockOverlayAdapter = mock(ElementListAdapter.class); |
| overlayAdapter.childAdapters.set(0, mockOverlayAdapter); |
| |
| adapter.unbindModel(); |
| assertThat(adapter.overlayAdapter).isNotNull(); |
| assertThat(overlayAdapter.childAdapters).isEmpty(); |
| verify(mockOverlayAdapter).releaseAdapter(); |
| } |
| |
| @Test |
| public void unbindModel_affectsVisibilityCalculations() { |
| VisibilityBindingRef visibilityBinding = |
| VisibilityBindingRef.newBuilder().setBindingId("vis").build(); |
| Element element = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder() |
| .setDefaultVisibility(Visibility.INVISIBLE) |
| .setOverridingBoundVisibility(visibilityBinding)) |
| .build(); |
| when(frameContext.getVisibilityFromBinding(visibilityBinding)).thenReturn(Visibility.VISIBLE); |
| |
| adapter.createAdapter(element, element, frameContext); |
| assertThat(adapter.getVisibilityForElement(element, frameContext)) |
| .isEqualTo(Visibility.INVISIBLE); |
| |
| adapter.bindModel(element, element, frameContext); |
| assertThat(adapter.getVisibilityForElement(element, frameContext)) |
| .isEqualTo(Visibility.VISIBLE); |
| |
| adapter.unbindModel(); |
| assertThat(adapter.getVisibilityForElement(element, frameContext)) |
| .isEqualTo(Visibility.INVISIBLE); |
| } |
| |
| @Test |
| public void releaseAdapter_callsOnReleaseAdapter() { |
| Element defaultElement = Element.getDefaultInstance(); |
| |
| adapter.createAdapter(defaultElement, defaultElement, frameContext); |
| assertThat(adapter.testAdapterCreated).isTrue(); |
| |
| adapter.releaseAdapter(); |
| assertThat(adapter.testAdapterCreated).isFalse(); |
| } |
| |
| @Test |
| public void releaseAdapter_removesOverlays() { |
| Element elementWithOverlays = |
| Element.newBuilder() |
| .addOverlays( |
| Content.newBuilder() |
| .setElement( |
| Element.newBuilder().setElementList(ElementList.getDefaultInstance()))) |
| .build(); |
| |
| adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext); |
| adapter.bindModel(elementWithOverlays, elementWithOverlays, frameContext); |
| View adapterView = adapter.getBaseView(); |
| FrameLayout overlayView = (FrameLayout) adapter.getView(); |
| |
| ElementStackAdapter mockOverlayAdapter = mock(ElementStackAdapter.class); |
| when(mockOverlayAdapter.getKey()).thenReturn(SingletonKeySupplier.SINGLETON_KEY); |
| adapter.overlayAdapter = mockOverlayAdapter; |
| |
| adapter.releaseAdapter(); |
| |
| assertThat(adapter.getView()).isSameInstanceAs(adapterView); |
| assertThat(adapter.getView()).isSameInstanceAs(adapter.getBaseView()); |
| assertThat(overlayView.getChildCount()).isEqualTo(0); |
| verify(mockOverlayAdapter).releaseAdapter(); |
| assertThat(adapter.overlayAdapter).isNull(); |
| } |
| |
| @Test |
| public void releaseAdapter_resetsVisibility() { |
| Element defaultElement = Element.getDefaultInstance(); |
| |
| adapter.createAdapter(defaultElement, defaultElement, frameContext); |
| adapter.setVisibilityOnView(Visibility.INVISIBLE); |
| assertThat(adapter.getBaseView().getVisibility()).isEqualTo(View.INVISIBLE); |
| |
| adapter.releaseAdapter(); |
| assertThat(adapter.getBaseView().getVisibility()).isEqualTo(View.VISIBLE); |
| } |
| |
| @Test |
| public void releaseAdapter_resetsDimensions() { |
| Element defaultElement = Element.getDefaultInstance(); |
| |
| adapter.createAdapter(defaultElement, defaultElement, frameContext); |
| adapter.setDims(123, 456); |
| |
| adapter.releaseAdapter(); |
| |
| assertThat(adapter.getComputedWidthPx()).isEqualTo(DIMENSION_NOT_SET); |
| assertThat(adapter.getComputedHeightPx()).isEqualTo(DIMENSION_NOT_SET); |
| } |
| |
| @Test |
| public void setVisibility() { |
| adapter.createAdapter(Element.getDefaultInstance(), Element.getDefaultInstance(), frameContext); |
| adapter.getBaseView().setVisibility(View.GONE); |
| adapter.setVisibilityOnView(Visibility.VISIBLE); |
| assertThat(adapter.getBaseView().getVisibility()).isEqualTo(View.VISIBLE); |
| adapter.setVisibilityOnView(Visibility.INVISIBLE); |
| assertThat(adapter.getBaseView().getVisibility()).isEqualTo(View.INVISIBLE); |
| adapter.setVisibilityOnView(Visibility.GONE); |
| assertThat(adapter.getBaseView().getVisibility()).isEqualTo(View.GONE); |
| adapter.setVisibilityOnView(Visibility.VISIBILITY_UNSPECIFIED); |
| assertThat(adapter.getBaseView().getVisibility()).isEqualTo(View.VISIBLE); |
| } |
| |
| @Test |
| public void getVisibilityForElement() { |
| VisibilityBindingRef bindingRef = |
| VisibilityBindingRef.newBuilder().setBindingId("binding").build(); |
| Element elementWithDefaultVisibility = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder().setDefaultVisibility(Visibility.INVISIBLE)) |
| .build(); |
| Element elementWithVisibilityOverride = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder() |
| .setDefaultVisibility(Visibility.INVISIBLE) |
| .setOverridingBoundVisibility(bindingRef)) |
| .build(); |
| |
| // Default visibility; no override |
| assertThat(adapter.getVisibilityForElement(elementWithDefaultVisibility, frameContext)) |
| .isEqualTo(Visibility.INVISIBLE); |
| |
| // Don't look up override when not binding |
| when(frameContext.getVisibilityFromBinding(bindingRef)).thenReturn(Visibility.GONE); |
| assertThat(adapter.getVisibilityForElement(elementWithVisibilityOverride, frameContext)) |
| .isEqualTo(Visibility.INVISIBLE); |
| |
| adapter.bindModel(Element.getDefaultInstance(), frameContext); |
| |
| // Look up override successfully |
| when(frameContext.getVisibilityFromBinding(bindingRef)).thenReturn(Visibility.GONE); |
| assertThat(adapter.getVisibilityForElement(elementWithVisibilityOverride, frameContext)) |
| .isEqualTo(Visibility.GONE); |
| |
| // Override not found; use default |
| when(frameContext.getVisibilityFromBinding(bindingRef)).thenReturn(null); |
| assertThat(adapter.getVisibilityForElement(elementWithVisibilityOverride, frameContext)) |
| .isEqualTo(Visibility.INVISIBLE); |
| } |
| |
| @Config(shadows = {ExtendedShadowView.class}) |
| @Test |
| public void getOnFullViewActions() { |
| Frame frame = Frame.newBuilder().setTag("FRAME").build(); |
| when(frameContext.getFrame()).thenReturn(frame); |
| View viewport = new View(context); |
| |
| // No actions defined |
| adapter.createAdapter(Element.getDefaultInstance(), frameContext); |
| adapter.bindModel(Element.getDefaultInstance(), frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verifyZeroInteractions(actionHandler); |
| adapter.releaseAdapter(); |
| |
| // Actions defined, but not fullview actions |
| Element elementWithNoViewActions = |
| Element.newBuilder() |
| .setActions(Actions.newBuilder().setOnClickAction(Action.newBuilder().build())) |
| .build(); |
| adapter.createAdapter(elementWithNoViewActions, frameContext); |
| adapter.bindModel(elementWithNoViewActions, frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verifyZeroInteractions(actionHandler); |
| adapter.unbindModel(); |
| adapter.releaseAdapter(); |
| |
| // Actions defined, but not fully visible |
| ExtendedShadowView viewShadow = Shadow.extract(adapter.getView()); |
| viewShadow.setAttachedToWindow(false); |
| Actions fullViewActions = |
| Actions.newBuilder() |
| .addOnViewActions(VisibilityAction.newBuilder().setAction(Action.newBuilder().build())) |
| .build(); |
| Element elementWithActions = Element.newBuilder().setActions(fullViewActions).build(); |
| adapter.createAdapter(elementWithActions, frameContext); |
| adapter.bindModel(elementWithActions, frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verifyZeroInteractions(actionHandler); |
| adapter.unbindModel(); |
| adapter.releaseAdapter(); |
| |
| // Actions defined, and fully visible |
| ExtendedShadowView viewportShadow = Shadow.extract(viewport); |
| viewportShadow.setLocationInWindow(0, 0); |
| viewportShadow.setHeight(100); |
| viewportShadow.setWidth(100); |
| viewShadow.setLocationInWindow(10, 10); |
| viewShadow.setHeight(50); |
| viewShadow.setWidth(50); |
| viewShadow.setAttachedToWindow(true); |
| |
| adapter.createAdapter(elementWithActions, frameContext); |
| adapter.bindModel(elementWithActions, frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verify(actionHandler) |
| .handleAction( |
| same(fullViewActions.getOnViewActions(0).getAction()), |
| eq(ActionType.VIEW), |
| eq(frame), |
| eq(view), |
| eq(LogData.getDefaultInstance())); |
| } |
| |
| @Config(shadows = {ExtendedShadowView.class}) |
| @Test |
| public void getOnPartialViewActions() { |
| Frame frame = Frame.newBuilder().setTag("FRAME").build(); |
| when(frameContext.getFrame()).thenReturn(frame); |
| View viewport = new View(context); |
| |
| // No actions defined |
| adapter.createAdapter(Element.getDefaultInstance(), frameContext); |
| adapter.bindModel(Element.getDefaultInstance(), frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verifyZeroInteractions(actionHandler); |
| adapter.releaseAdapter(); |
| |
| // Actions defined, but not partial view actions |
| Element elementWithNoViewActions = |
| Element.newBuilder() |
| .setActions(Actions.newBuilder().setOnClickAction(Action.newBuilder().build())) |
| .build(); |
| adapter.createAdapter(elementWithNoViewActions, frameContext); |
| adapter.bindModel(elementWithNoViewActions, frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verifyZeroInteractions(actionHandler); |
| adapter.unbindModel(); |
| adapter.releaseAdapter(); |
| |
| // Actions defined, but not attached to window |
| ExtendedShadowView viewShadow = Shadow.extract(adapter.getView()); |
| viewShadow.setAttachedToWindow(false); |
| Actions partialViewActions = |
| Actions.newBuilder() |
| .addOnViewActions( |
| VisibilityAction.newBuilder() |
| .setAction(Action.newBuilder().build()) |
| .setProportionVisible(0.01f)) |
| .build(); |
| Element elementWithActions = Element.newBuilder().setActions(partialViewActions).build(); |
| adapter.createAdapter(elementWithActions, frameContext); |
| adapter.bindModel(elementWithActions, frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verifyZeroInteractions(actionHandler); |
| adapter.unbindModel(); |
| adapter.releaseAdapter(); |
| |
| // Actions defined, and partially visible |
| ExtendedShadowView viewportShadow = Shadow.extract(viewport); |
| viewportShadow.setLocationInWindow(0, 0); |
| viewportShadow.setHeight(100); |
| viewportShadow.setWidth(100); |
| viewShadow.setLocationInWindow(10, 10); |
| viewShadow.setHeight(200); |
| viewShadow.setWidth(200); |
| viewShadow.setAttachedToWindow(true); |
| |
| adapter.createAdapter(elementWithActions, frameContext); |
| adapter.bindModel(elementWithActions, frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verify(actionHandler) |
| .handleAction( |
| same(partialViewActions.getOnViewActions(0).getAction()), |
| eq(ActionType.VIEW), |
| eq(frame), |
| eq(view), |
| eq(LogData.getDefaultInstance())); |
| |
| // Actions defined, and fully visible |
| viewShadow.setHeight(50); |
| viewShadow.setWidth(50); |
| |
| adapter.createAdapter(elementWithActions, frameContext); |
| adapter.bindModel(elementWithActions, frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verify(actionHandler, times(2)) |
| .handleAction( |
| same(partialViewActions.getOnViewActions(0).getAction()), |
| eq(ActionType.VIEW), |
| eq(frame), |
| eq(view), |
| eq(LogData.getDefaultInstance())); |
| } |
| |
| @Config(shadows = {ExtendedShadowView.class}) |
| @Test |
| public void triggerHideActions() { |
| Frame frame = Frame.newBuilder().setTag("FRAME").build(); |
| when(frameContext.getFrame()).thenReturn(frame); |
| View viewport = new View(context); |
| |
| // No actions defined |
| adapter.createAdapter(Element.getDefaultInstance(), frameContext); |
| adapter.bindModel(Element.getDefaultInstance(), frameContext); |
| adapter.triggerHideActions(frameContext); |
| verifyZeroInteractions(actionHandler); |
| adapter.releaseAdapter(); |
| |
| // Actions defined, but not hide actions |
| Element elementWithNoViewActions = |
| Element.newBuilder() |
| .setActions(Actions.newBuilder().setOnClickAction(Action.getDefaultInstance())) |
| .build(); |
| adapter.createAdapter(elementWithNoViewActions, frameContext); |
| adapter.bindModel(elementWithNoViewActions, frameContext); |
| adapter.triggerHideActions(frameContext); |
| verifyZeroInteractions(actionHandler); |
| adapter.unbindModel(); |
| adapter.releaseAdapter(); |
| |
| // Trigger hide actions on the fully-visible view to make hide actions not active. |
| Action hideAction = Action.newBuilder().build(); |
| Actions hideActions = |
| Actions.newBuilder() |
| .addOnHideActions(VisibilityAction.newBuilder().setAction(hideAction)) |
| .build(); |
| Element elementWithActions = Element.newBuilder().setActions(hideActions).build(); |
| ExtendedShadowView viewShadow = Shadow.extract(adapter.getView()); |
| ExtendedShadowView viewportShadow = Shadow.extract(viewport); |
| viewportShadow.setLocationInWindow(0, 0); |
| viewportShadow.setHeight(100); |
| viewportShadow.setWidth(100); |
| viewShadow.setLocationInWindow(10, 10); |
| viewShadow.setHeight(50); |
| viewShadow.setWidth(50); |
| viewShadow.setAttachedToWindow(true); |
| |
| adapter.createAdapter(elementWithActions, frameContext); |
| adapter.bindModel(elementWithActions, frameContext); |
| adapter.triggerViewActions(viewport, frameContext); |
| verifyZeroInteractions(actionHandler); |
| |
| // Trigger hide actions |
| adapter.triggerHideActions(frameContext); |
| |
| verify(actionHandler) |
| .handleAction( |
| same(hideAction), |
| eq(ActionType.VIEW), |
| eq(frame), |
| eq(view), |
| eq(LogData.getDefaultInstance())); |
| |
| // Ensure actions do not trigger again |
| adapter.triggerHideActions(frameContext); |
| |
| verify(actionHandler) // Only once, not twice |
| .handleAction( |
| same(hideAction), |
| eq(ActionType.VIEW), |
| eq(frame), |
| eq(view), |
| eq(LogData.getDefaultInstance())); |
| } |
| |
| @Test |
| public void createRoundedCorners() { |
| RoundedCorners roundedCorners = RoundedCorners.newBuilder().setRadiusDp(10).build(); |
| RoundedCornerWrapperView roundedCornerWrapperView = |
| new RoundedCornerWrapperView( |
| context, |
| roundedCorners, |
| maskCache, |
| Suppliers.of(false), |
| /*radiusOverride= */ 0, |
| Borders.getDefaultInstance(), |
| LEGACY_CORNERS_FLAG, |
| OUTLINE_CORNERS_FLAG); |
| when(styleProvider.hasRoundedCorners()).thenReturn(true); |
| when(styleProvider.createWrapperView( |
| context, maskCache, LEGACY_CORNERS_FLAG, OUTLINE_CORNERS_FLAG)) |
| .thenReturn(roundedCornerWrapperView); |
| |
| adapter.createAdapter(Element.getDefaultInstance(), frameContext); |
| |
| assertThat(adapter.getView()).isSameInstanceAs(roundedCornerWrapperView); |
| } |
| |
| @Test |
| public void setVisibilityWithRoundedCorners() { |
| RoundedCorners roundedCorners = RoundedCorners.newBuilder().setRadiusDp(10).build(); |
| RoundedCornerWrapperView roundedCornerWrapperView = |
| new RoundedCornerWrapperView( |
| context, |
| roundedCorners, |
| maskCache, |
| Suppliers.of(false), |
| /*radiusOverride= */ 0, |
| Borders.getDefaultInstance(), |
| LEGACY_CORNERS_FLAG, |
| OUTLINE_CORNERS_FLAG); |
| when(styleProvider.hasRoundedCorners()).thenReturn(true); |
| when(styleProvider.createWrapperView( |
| context, maskCache, LEGACY_CORNERS_FLAG, OUTLINE_CORNERS_FLAG)) |
| .thenReturn(roundedCornerWrapperView); |
| |
| VisibilityBindingRef visibilityBinding = |
| VisibilityBindingRef.newBuilder().setBindingId("visibility").build(); |
| when(frameContext.getVisibilityFromBinding(visibilityBinding)).thenReturn(Visibility.VISIBLE); |
| |
| Element element = |
| Element.newBuilder() |
| .setVisibilityState( |
| VisibilityState.newBuilder() |
| .setDefaultVisibility(Visibility.GONE) |
| .setOverridingBoundVisibility(visibilityBinding)) |
| .build(); |
| |
| adapter.createAdapter(element, frameContext); |
| |
| FrameLayout parentView = new FrameLayout(context); |
| parentView.addView(adapter.getView()); |
| |
| adapter.bindModel(element, frameContext); |
| |
| assertThat(adapter.getView()).isSameInstanceAs(roundedCornerWrapperView); |
| } |
| |
| @Test |
| public void getStyleIdsStack() { |
| StyleIdsStack style = StyleIdsStack.newBuilder().addStyleIds("style").build(); |
| Element elementWithStyle = Element.newBuilder().setStyleReferences(style).build(); |
| |
| adapter.createAdapter(elementWithStyle, frameContext); |
| |
| assertThat(adapter.getElementStyleIdsStack()).isSameInstanceAs(style); |
| |
| Element elementWithNoStyle = Element.getDefaultInstance(); |
| |
| adapter.createAdapter(elementWithNoStyle, frameContext); |
| |
| assertThat(adapter.getElementStyleIdsStack()).isEqualTo(TestElementAdapter.SUB_ELEMENT_STYLE); |
| } |
| |
| // Dummy implementation |
| static class TestElementAdapter extends ElementAdapter<View, Object> { |
| static final String DEFAULT_MODEL = "MODEL"; |
| static final StyleIdsStack SUB_ELEMENT_STYLE = |
| StyleIdsStack.newBuilder().addStyleIds("deprecated").build(); |
| |
| boolean testAdapterCreated = false; |
| boolean testAdapterBound = false; |
| |
| View adapterView; |
| |
| TestElementAdapter(Context context, AdapterParameters parameters, View adapterView) { |
| super(context, parameters, adapterView); |
| this.adapterView = adapterView; |
| } |
| |
| @Override |
| protected Object getModelFromElement(Element baseElement) { |
| return DEFAULT_MODEL; |
| } |
| |
| @Override |
| protected void onCreateAdapter(Object model, Element baseElement, FrameContext frameContext) { |
| testAdapterCreated = true; |
| } |
| |
| @Override |
| protected void onBindModel(Object model, Element baseElement, FrameContext frameContext) { |
| testAdapterBound = true; |
| } |
| |
| @Override |
| StyleIdsStack getSubElementStyleIdsStack() { |
| return SUB_ELEMENT_STYLE; |
| } |
| |
| @Override |
| protected void onUnbindModel() { |
| testAdapterBound = false; |
| } |
| |
| @Override |
| protected void onReleaseAdapter() { |
| testAdapterCreated = false; |
| } |
| |
| public void setDims(int width, int height) { |
| widthPx = width; |
| heightPx = height; |
| } |
| } |
| |
| } |