blob: 2236cc4ed91876e6de98a5d556672beab790e715 [file] [log] [blame]
// 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.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
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.ViewGroup.MarginLayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
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.common.ui.LayoutUtils;
import com.google.android.libraries.feed.host.config.DebugBehavior;
import com.google.android.libraries.feed.piet.DebugLogger.MessageType;
import com.google.android.libraries.feed.piet.GridRowAdapter.KeySupplier;
import com.google.android.libraries.feed.piet.host.ActionHandler;
import com.google.android.libraries.feed.piet.host.AssetProvider;
import com.google.android.libraries.feed.piet.host.EventLogger;
import com.google.android.libraries.feed.piet.ui.GridRowView;
import com.google.search.now.ui.piet.BindingRefsProto.ElementBindingRef;
import com.google.search.now.ui.piet.BindingRefsProto.GridCellWidthBindingRef;
import com.google.search.now.ui.piet.BindingRefsProto.StyleBindingRef;
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.CustomElement;
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.GridCell;
import com.google.search.now.ui.piet.ElementsProto.GridCellWidth;
import com.google.search.now.ui.piet.ElementsProto.GridCellWidth.ContentWidth;
import com.google.search.now.ui.piet.ElementsProto.GridRow;
import com.google.search.now.ui.piet.ElementsProto.ImageElement;
import com.google.search.now.ui.piet.ElementsProto.TextElement;
import com.google.search.now.ui.piet.ElementsProto.Visibility;
import com.google.search.now.ui.piet.ErrorsProto.ErrorCode;
import com.google.search.now.ui.piet.ImagesProto.Image;
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.EdgeWidths;
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 java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
/** Tests of the {@link GridRowAdapter}. */
@RunWith(RobolectricTestRunner.class)
public class GridRowAdapterTest {
private static final String GRID_STYLE_ID = "cybercat";
private static final StyleIdsStack GRID_STYLES =
StyleIdsStack.newBuilder().addStyleIds(GRID_STYLE_ID).build();
private static final Element DEFAULT_ELEMENT =
Element.newBuilder().setElementStack(ElementStack.getDefaultInstance()).build();
private static final Content DEFAULT_CONTENT =
Content.newBuilder().setElement(DEFAULT_ELEMENT).build();
private static final Content TEXT_CONTENT =
Content.newBuilder()
.setElement(
Element.newBuilder()
.setTextElement(
TextElement.newBuilder()
.setParameterizedText(ParameterizedText.newBuilder().setText("TheGrid"))))
.build();
private static final String BINDING_ID = "stripes";
private static final ElementBindingRef ELEMENT_BINDING =
ElementBindingRef.newBuilder().setBindingId(BINDING_ID).build();
private static final Content BOUND_CONTENTS =
Content.newBuilder().setBoundElement(ELEMENT_BINDING).build();
private static final Element GRID_ROW_WITH_BOUND_CELL =
Element.newBuilder()
.setGridRow(
GridRow.newBuilder().addCells(GridCell.newBuilder().setContent(BOUND_CONTENTS)))
.build();
private Context context;
private AdapterParameters adapterParameters;
@Mock private ActionHandler actionHandler;
@Mock private FrameContext frameContext;
@Mock private StyleProvider styleProvider;
@Mock private HostProviders hostProviders;
@Mock private AssetProvider assetProvider;
private GridRowAdapter adapter;
@Before
public void setUp() throws Exception {
initMocks(this);
context = Robolectric.buildActivity(Activity.class).get();
when(hostProviders.getAssetProvider()).thenReturn(assetProvider);
when(assetProvider.isRtL()).thenReturn(false);
when(assetProvider.isRtLSupplier()).thenReturn(Suppliers.of(false));
when(frameContext.makeStyleFor(GRID_STYLES)).thenReturn(styleProvider);
when(frameContext.getActionHandler()).thenReturn(actionHandler);
when(frameContext.filterImageSourcesByMediaQueryCondition(any(Image.class)))
.thenAnswer(invocation -> invocation.getArguments()[0]);
when(frameContext.reportMessage(anyInt(), any(), anyString()))
.thenAnswer(invocation -> invocation.getArguments()[2]);
when(styleProvider.getPadding()).thenReturn(EdgeWidths.getDefaultInstance());
when(styleProvider.getRoundedCorners()).thenReturn(RoundedCorners.getDefaultInstance());
adapterParameters =
new AdapterParameters(
context, Suppliers.of(null), hostProviders, new FakeClock(), false, false);
when(frameContext.makeStyleFor(StyleIdsStack.getDefaultInstance()))
.thenReturn(adapterParameters.defaultStyleProvider);
adapter = new KeySupplier().getAdapter(context, adapterParameters);
}
@Test
public void testViewDoesNotClip() {
assertThat(adapter.getBaseView().getClipToPadding()).isFalse();
}
@Test
public void testOnCreateAdapter_makesRow() {
// We create an adapter for the inline content, but not for the bound content.
GridRow model =
GridRow.newBuilder()
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT))
.addCells(GridCell.newBuilder().setContent(BOUND_CONTENTS))
.build();
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(BindingValue.newBuilder().setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(asElement(model), frameContext);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(1);
assertThat(adapter.getBaseView().getBaseline()).isEqualTo(-1);
assertThat(adapter.childAdapters).hasSize(1);
assertThat(adapter.getBaseView().getChildAt(0))
.isSameAs(adapter.childAdapters.get(0).getView());
}
@Test
public void testOnCreateAdapter_missingContentIsException() {
GridRow model = GridRow.newBuilder().addCells(GridCell.getDefaultInstance()).build();
assertThatRunnable(() -> adapter.createAdapter(asElement(model), frameContext))
.throwsAnExceptionOfType(PietFatalException.class)
.that()
.hasMessageThat()
.contains("Unhandled Content type: CONTENTTYPE_NOT_SET");
}
@Test
public void testOnCreateAdapter_setsGridRowStyles() {
GridRow model =
GridRow.newBuilder()
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT))
.setStyleReferences(GRID_STYLES)
.build();
adapter.createAdapter(asElement(model), frameContext);
verify(frameContext).makeStyleFor(GRID_STYLES);
verify(styleProvider).applyElementStyles(adapter);
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_widthDefaultsToWeight() {
GridRow model =
GridRow.newBuilder()
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT).clearWidth())
.build();
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams())
.isInstanceOf(LinearLayout.LayoutParams.class);
LayoutParams params =
(LinearLayout.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams();
assertThat(params.width).isEqualTo(0);
assertThat(params.weight).isEqualTo(1.0f);
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_widthDp() {
int widthDp = 123;
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setContent(DEFAULT_CONTENT)
.setWidth(GridCellWidth.newBuilder().setDp(widthDp)))
.build();
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams())
.isInstanceOf(LinearLayout.LayoutParams.class);
LayoutParams params =
(LinearLayout.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams();
assertThat(params.width).isEqualTo((int) LayoutUtils.dpToPx(widthDp, context));
assertThat(params.weight).isEqualTo(0.0f);
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_widthWeight() {
int widthWeight = 321;
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setContent(DEFAULT_CONTENT)
.setWidth(GridCellWidth.newBuilder().setWeight(widthWeight)))
.build();
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams())
.isInstanceOf(LinearLayout.LayoutParams.class);
LayoutParams params =
(LinearLayout.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams();
assertThat(params.weight).isEqualTo((float) widthWeight);
assertThat(params.width).isEqualTo(0);
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_widthBinding() {
GridCellWidthBindingRef widthBindingRef =
GridCellWidthBindingRef.newBuilder().setBindingId("fatcat").build();
int widthDp = 222;
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder().setContent(DEFAULT_CONTENT).setWidthBinding(widthBindingRef))
.build();
when(frameContext.getGridCellWidthFromBinding(widthBindingRef))
.thenReturn(GridCellWidth.newBuilder().setDp(widthDp).build());
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams())
.isInstanceOf(LinearLayout.LayoutParams.class);
LayoutParams params =
(LinearLayout.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams();
assertThat(params.width).isEqualTo((int) LayoutUtils.dpToPx(widthDp, context));
assertThat(params.weight).isEqualTo(0.0f);
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_invalidContentWidth() {
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setContent(DEFAULT_CONTENT)
.setWidth(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.INVALID_CONTENT_WIDTH)))
.build();
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
LayoutParams params =
(LinearLayout.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams();
assertThat(params.width).isEqualTo(LayoutParams.WRAP_CONTENT);
assertThat(params.weight).isEqualTo(0.0f);
verify(frameContext)
.reportMessage(
MessageType.WARNING,
ErrorCode.ERR_GRID_CELL_WIDTH_WITHOUT_CONTENTS,
"Invalid content width: INVALID_CONTENT_WIDTH");
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_widthOfChildAdapter() {
StyleIdsStack childStyles = StyleIdsStack.newBuilder().addStyleIds("child").build();
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder().setContentWidth(ContentWidth.CONTENT_WIDTH))
.setContent(
Content.newBuilder()
.setElement(
Element.newBuilder()
.setImageElement(
ImageElement.newBuilder()
.setStyleReferences(childStyles)
.setImage(Image.getDefaultInstance())))))
.build();
StyleProvider childStyleProvider = mock(StyleProvider.class);
when(childStyleProvider.hasWidth()).thenReturn(true);
when(childStyleProvider.getWidthSpecPx(context)).thenReturn(456);
when(childStyleProvider.getRoundedCorners()).thenReturn(RoundedCorners.getDefaultInstance());
when(childStyleProvider.getScaleType()).thenReturn(ImageView.ScaleType.CENTER_CROP);
when(frameContext.makeStyleFor(childStyles)).thenReturn(childStyleProvider);
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams())
.isInstanceOf(LinearLayout.LayoutParams.class);
LayoutParams params =
(LinearLayout.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams();
assertThat(params.width).isEqualTo((int) LayoutUtils.dpToPx(456, context));
assertThat(params.weight).isEqualTo(0.0f);
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_heightOfChildAdapter() {
StyleIdsStack childStyles = StyleIdsStack.newBuilder().addStyleIds("child").build();
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setContent(
Content.newBuilder()
.setElement(
Element.newBuilder()
.setImageElement(
ImageElement.newBuilder()
.setStyleReferences(childStyles)
.setImage(Image.getDefaultInstance())))))
.build();
StyleProvider childStyleProvider = mock(StyleProvider.class);
when(childStyleProvider.hasHeight()).thenReturn(true);
when(childStyleProvider.getHeightSpecPx(context)).thenReturn(123);
when(childStyleProvider.getRoundedCorners()).thenReturn(RoundedCorners.getDefaultInstance());
when(childStyleProvider.getScaleType()).thenReturn(ImageView.ScaleType.CENTER_CROP);
when(frameContext.makeStyleFor(childStyles)).thenReturn(childStyleProvider);
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams())
.isInstanceOf(LinearLayout.LayoutParams.class);
LayoutParams params =
(LinearLayout.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams();
assertThat(params.height).isEqualTo((int) LayoutUtils.dpToPx(123, context));
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_heightOfChildAdapterNotDefined() {
StyleIdsStack childStyles = StyleIdsStack.newBuilder().addStyleIds("child").build();
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setContent(
Content.newBuilder()
.setElement(
Element.newBuilder()
.setImageElement(
ImageElement.newBuilder()
.setImage(Image.getDefaultInstance())))))
.build();
StyleProvider childStyleProvider = mock(StyleProvider.class);
when(childStyleProvider.hasHeight()).thenReturn(false);
when(childStyleProvider.getHeightSpecPx(context)).thenReturn(DIMENSION_NOT_SET);
when(childStyleProvider.getRoundedCorners()).thenReturn(RoundedCorners.getDefaultInstance());
when(frameContext.makeStyleFor(childStyles)).thenReturn(childStyleProvider);
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams())
.isInstanceOf(LinearLayout.LayoutParams.class);
LayoutParams params =
(LinearLayout.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams();
assertThat(params.height).isEqualTo(LayoutParams.MATCH_PARENT);
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_margins() {
StyleIdsStack childStyles = StyleIdsStack.newBuilder().addStyleIds("child").build();
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setContent(
Content.newBuilder()
.setElement(
Element.newBuilder()
.setStyleReferences(childStyles)
.setElementStack(ElementStack.getDefaultInstance()))))
.build();
StyleProvider childStyleProvider = mock(StyleProvider.class);
when(childStyleProvider.getPadding()).thenReturn(EdgeWidths.getDefaultInstance());
when(childStyleProvider.getRoundedCorners()).thenReturn(RoundedCorners.getDefaultInstance());
when(frameContext.makeStyleFor(childStyles)).thenReturn(childStyleProvider);
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
verify(childStyleProvider)
.applyMargins(
context, (MarginLayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams());
}
@Test
public void testOnBindModel_setsLayoutParamsOnCell_verticalGravityCenter() {
GridRow gridRowTop =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setContent(
Content.newBuilder()
.setElement(
DEFAULT_ELEMENT
.toBuilder()
.setGravityVertical(GravityVertical.GRAVITY_MIDDLE))))
.build();
adapter.createAdapter(asElement(gridRowTop), frameContext);
adapter.bindModel(asElement(gridRowTop), frameContext);
ElementStackAdapter cellAdapter = (ElementStackAdapter) adapter.childAdapters.get(0);
LayoutParams params = (LinearLayout.LayoutParams) cellAdapter.getView().getLayoutParams();
assertThat(params.gravity).isEqualTo(Gravity.CENTER_VERTICAL);
}
@Test
public void testOnBindModel_collapsibleCells_valid() {
GridRow gridRow =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder().setContentWidth(ContentWidth.CONTENT_WIDTH))
.setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.CONTENT_WIDTH)
.setIsCollapsible(true))
.setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(GridCellWidth.newBuilder().setDp(123))
.setContent(TEXT_CONTENT))
.build();
adapter.createAdapter(asElement(gridRow), frameContext);
adapter.bindModel(asElement(gridRow), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams().width)
.isEqualTo(LayoutParams.WRAP_CONTENT);
assertThat(adapter.getBaseView().getChildAt(1).getLayoutParams().width)
.isEqualTo(LayoutParams.WRAP_CONTENT);
assertThat(
((GridRowView.LayoutParams) adapter.getBaseView().getChildAt(1).getLayoutParams())
.getIsCollapsible())
.isTrue();
assertThat(adapter.getBaseView().getChildAt(2).getLayoutParams().width)
.isEqualTo((int) LayoutUtils.dpToPx(123, context));
}
@Test
public void testOnBindModel_collapsibleCells_multipleCollapsible() {
GridRow gridRow =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.CONTENT_WIDTH)
.setIsCollapsible(true))
.setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(GridCellWidth.newBuilder().setDp(456).setIsCollapsible(true))
.setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(GridCellWidth.newBuilder().setDp(123))
.setContent(TEXT_CONTENT))
.build();
adapter.createAdapter(asElement(gridRow), frameContext);
adapter.bindModel(asElement(gridRow), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams().width)
.isEqualTo(LayoutParams.WRAP_CONTENT);
assertThat(
((GridRowView.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams())
.getIsCollapsible())
.isTrue();
assertThat(adapter.getBaseView().getChildAt(1).getLayoutParams().width).isEqualTo(456);
assertThat(
((GridRowView.LayoutParams) adapter.getBaseView().getChildAt(1).getLayoutParams())
.getIsCollapsible())
.isTrue();
assertThat(adapter.getBaseView().getChildAt(2).getLayoutParams().width)
.isEqualTo((int) LayoutUtils.dpToPx(123, context));
}
@Test
public void testOnBindModel_collapsibleCells_mixingCollapsibleAndWeight() {
GridRow gridRow =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.CONTENT_WIDTH)
.setIsCollapsible(true))
.setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(GridCellWidth.newBuilder().setWeight(4))
.setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(GridCellWidth.newBuilder().setDp(123))
.setContent(TEXT_CONTENT))
.build();
adapter.createAdapter(asElement(gridRow), frameContext);
adapter.bindModel(asElement(gridRow), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams().width)
.isEqualTo(LayoutParams.WRAP_CONTENT);
assertThat(
((GridRowView.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams())
.getIsCollapsible())
.isTrue();
assertThat(adapter.getBaseView().getChildAt(1).getLayoutParams().width).isEqualTo(0);
assertThat(
((LinearLayout.LayoutParams) adapter.getBaseView().getChildAt(1).getLayoutParams())
.weight)
.isEqualTo(4.0f);
assertThat(adapter.getBaseView().getChildAt(2).getLayoutParams().width)
.isEqualTo((int) LayoutUtils.dpToPx(123, context));
}
@Test
public void testOnBindModel_recreatesBindingCells() {
Element cellWithOneElement =
Element.newBuilder()
.setElementList(
ElementList.newBuilder()
.addContents(
Content.newBuilder()
.setElement(
Element.newBuilder()
.setElementStack(ElementStack.getDefaultInstance()))))
.build();
Element cellWithTwoElements =
Element.newBuilder()
.setElementList(
ElementList.newBuilder()
.addContents(
Content.newBuilder()
.setElement(
Element.newBuilder()
.setElementStack(ElementStack.getDefaultInstance())))
.addContents(
Content.newBuilder()
.setElement(
Element.newBuilder()
.setElementStack(ElementStack.getDefaultInstance()))))
.build();
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(BindingValue.newBuilder().setElement(cellWithOneElement).build());
adapter.createAdapter(GRID_ROW_WITH_BOUND_CELL, frameContext);
// The cell adapter has not been created yet
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(BindingValue.newBuilder().setElement(cellWithTwoElements).build());
adapter.bindModel(GRID_ROW_WITH_BOUND_CELL, frameContext);
// The cell adapter creates its one view on bind.
assertThat(((LinearLayout) adapter.getBaseView().getChildAt(0)).getChildCount()).isEqualTo(2);
adapter.unbindModel();
// The cell adapter has been released.
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(BindingValue.newBuilder().setElement(cellWithOneElement).build());
adapter.bindModel(GRID_ROW_WITH_BOUND_CELL, frameContext);
// The cell adapter can bind to a different model.
assertThat(((LinearLayout) adapter.getBaseView().getChildAt(0)).getChildCount()).isEqualTo(1);
}
@Test
public void testOnBindModel_visibilityGone() {
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(
BindingValue.newBuilder().setBindingId(BINDING_ID).setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(GRID_ROW_WITH_BOUND_CELL, frameContext);
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(
BindingValue.newBuilder()
.setBindingId(BINDING_ID)
.setVisibility(Visibility.GONE)
.build());
adapter.bindModel(GRID_ROW_WITH_BOUND_CELL, frameContext);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
}
@Test
public void testOnBindModel_noContent() {
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(
BindingValue.newBuilder().setBindingId(BINDING_ID).setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(GRID_ROW_WITH_BOUND_CELL, frameContext);
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(BindingValue.newBuilder().setBindingId(BINDING_ID).build());
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
}
@Test
public void testOnBindModel_optionalAbsent() {
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(
BindingValue.newBuilder().setBindingId(BINDING_ID).setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(GRID_ROW_WITH_BOUND_CELL, frameContext);
ElementBindingRef optionalBinding = ELEMENT_BINDING.toBuilder().setIsOptional(true).build();
GridRow optionalBindingRow =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setContent(Content.newBuilder().setBoundElement(optionalBinding)))
.build();
when(frameContext.getElementBindingValue(optionalBinding))
.thenReturn(BindingValue.newBuilder().setBindingId(BINDING_ID).build());
adapter.bindModel(asElement(optionalBindingRow), frameContext);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
}
@Test
public void testOnBindModel_throwsExceptionOnCellCountMismatch() {
GridRow gridRowWithTwoElements =
GridRow.newBuilder()
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT))
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT))
.build();
GridRow gridRowWithOneElement =
GridRow.newBuilder().addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT)).build();
adapter.createAdapter(asElement(gridRowWithTwoElements), frameContext);
assertThatRunnable(() -> adapter.bindModel(asElement(gridRowWithOneElement), frameContext))
.throwsAnExceptionOfType(IllegalStateException.class)
.that()
.hasMessageThat()
.contains("Internal error in adapters per content");
}
@Test
public void testOnBindModel_setsStylesOnlyIfBindingIsDefined() {
GridRow gridRowWithStyle =
GridRow.newBuilder()
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT))
.setStyleReferences(GRID_STYLES)
.build();
adapter.createAdapter(asElement(gridRowWithStyle), frameContext);
verify(frameContext).makeStyleFor(GRID_STYLES);
// When we bind a new model, the style does not change.
StyleIdsStack otherStyles = StyleIdsStack.newBuilder().addStyleIds("ignored").build();
GridRow gridRowWithOtherStyle =
GridRow.newBuilder()
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT))
.setStyleReferences(otherStyles)
.build();
adapter.bindModel(asElement(gridRowWithOtherStyle), frameContext);
verify(frameContext, never()).makeStyleFor(otherStyles);
// If we bind a model that has a style binding, then the style does get re-applied.
StyleIdsStack styleWithBinding =
StyleIdsStack.newBuilder()
.setStyleBinding(StyleBindingRef.newBuilder().setBindingId("homewardbound"))
.build();
GridRow gridRowWithBoundStyle =
GridRow.newBuilder()
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT))
.setStyleReferences(styleWithBinding)
.build();
StyleProvider otherStyleProvider = mock(StyleProvider.class);
when(frameContext.makeStyleFor(styleWithBinding)).thenReturn(otherStyleProvider);
adapter.bindModel(asElement(gridRowWithBoundStyle), frameContext);
verify(frameContext).makeStyleFor(styleWithBinding);
verify(otherStyleProvider).applyElementStyles(adapter);
}
@Test
public void testOnBindModel_collapsibleCells_valid_boundWidth() {
GridCellWidthBindingRef widthBinding =
GridCellWidthBindingRef.newBuilder().setBindingId("width").build();
GridRow gridRow =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder().setContentWidth(ContentWidth.CONTENT_WIDTH))
.setContent(TEXT_CONTENT))
.addCells(GridCell.newBuilder().setWidthBinding(widthBinding).setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(GridCellWidth.newBuilder().setDp(123))
.setContent(TEXT_CONTENT))
.build();
when(frameContext.getGridCellWidthFromBinding(widthBinding))
.thenReturn(GridCellWidth.newBuilder().setDp(456).build());
adapter.createAdapter(asElement(gridRow), frameContext);
when(frameContext.getGridCellWidthFromBinding(widthBinding))
.thenReturn(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.CONTENT_WIDTH)
.setIsCollapsible(true)
.build());
adapter.bindModel(asElement(gridRow), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams().width)
.isEqualTo(LayoutParams.WRAP_CONTENT);
assertThat(adapter.getBaseView().getChildAt(1).getLayoutParams().width)
.isEqualTo(LayoutParams.WRAP_CONTENT);
assertThat(
((GridRowView.LayoutParams) adapter.getBaseView().getChildAt(1).getLayoutParams())
.getIsCollapsible())
.isTrue();
assertThat(adapter.getBaseView().getChildAt(2).getLayoutParams().width)
.isEqualTo((int) LayoutUtils.dpToPx(123, context));
}
@Test
public void testOnBindModel_collapsibleCells_multipleCollapsible_boundWidth() {
GridCellWidthBindingRef widthBinding =
GridCellWidthBindingRef.newBuilder().setBindingId("width").build();
GridRow gridRow =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.CONTENT_WIDTH)
.setIsCollapsible(true))
.setContent(TEXT_CONTENT))
.addCells(GridCell.newBuilder().setWidthBinding(widthBinding).setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(GridCellWidth.newBuilder().setDp(123))
.setContent(TEXT_CONTENT))
.build();
when(frameContext.getGridCellWidthFromBinding(widthBinding))
.thenReturn(GridCellWidth.newBuilder().setDp(456).build());
adapter.createAdapter(asElement(gridRow), frameContext);
when(frameContext.getGridCellWidthFromBinding(widthBinding))
.thenReturn(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.CONTENT_WIDTH)
.setIsCollapsible(true)
.build());
adapter.bindModel(asElement(gridRow), frameContext);
assertThat(adapter.getBaseView().getChildAt(0).getLayoutParams().width)
.isEqualTo(LayoutParams.WRAP_CONTENT);
assertThat(
((GridRowView.LayoutParams) adapter.getBaseView().getChildAt(0).getLayoutParams())
.getIsCollapsible())
.isTrue();
assertThat(adapter.getBaseView().getChildAt(1).getLayoutParams().width)
.isEqualTo(LayoutParams.WRAP_CONTENT);
assertThat(
((GridRowView.LayoutParams) adapter.getBaseView().getChildAt(1).getLayoutParams())
.getIsCollapsible())
.isTrue();
assertThat(adapter.getBaseView().getChildAt(2).getLayoutParams().width)
.isEqualTo((int) LayoutUtils.dpToPx(123, context));
}
@Test
public void testUnbindModel() {
GridRow model =
GridRow.newBuilder()
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT))
.addCells(
GridCell.newBuilder()
.setContent(Content.newBuilder().setBoundElement(ELEMENT_BINDING)))
.build();
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(BindingValue.newBuilder().setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
adapter.unbindModel();
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(1);
assertThat(adapter.childAdapters).hasSize(1);
// The inline adapter has been unbound.
assertThat(adapter.childAdapters.get(0).getRawModel()).isNull();
}
@Test
public void testUnbindModel_worksTwice() {
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.CONTENT_WIDTH)
.setIsCollapsible(true))
.setContent(TEXT_CONTENT))
.build();
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
adapter.unbindModel();
adapter.unbindModel();
// assert no failure.
}
@Test
public void testReleaseAdapter() {
GridRow model =
GridRow.newBuilder()
.addCells(GridCell.newBuilder().setContent(DEFAULT_CONTENT))
.addCells(
GridCell.newBuilder()
.setContent(Content.newBuilder().setBoundElement(ELEMENT_BINDING)))
.build();
when(frameContext.getElementBindingValue(ELEMENT_BINDING))
.thenReturn(BindingValue.newBuilder().setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
adapter.unbindModel();
adapter.releaseAdapter();
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
}
@Test
public void testReleaseAdapter_collapsibleCells() {
GridRow model =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.CONTENT_WIDTH)
.setIsCollapsible(true))
.setContent(TEXT_CONTENT))
.build();
adapter.createAdapter(asElement(model), frameContext);
adapter.bindModel(asElement(model), frameContext);
adapter.unbindModel();
adapter.releaseAdapter();
assertThat(adapter.getBaseView().hasCollapsibleCells()).isFalse();
}
/**
* Mini integration test to ensure that WRAP_CONTENT is set on GridRowAdapter when it is within a
* hierarchy
*/
@Test
public void testCollapsibleCells_integration() {
GridRow gridRow =
GridRow.newBuilder()
.addCells(
GridCell.newBuilder()
.setWidth(GridCellWidth.newBuilder().setDp(123))
.setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder()
.setContentWidth(ContentWidth.CONTENT_WIDTH)
.setIsCollapsible(true))
.setContent(TEXT_CONTENT))
.addCells(
GridCell.newBuilder()
.setWidth(
GridCellWidth.newBuilder().setContentWidth(ContentWidth.CONTENT_WIDTH))
.setContent(TEXT_CONTENT))
.build();
Frame frame =
Frame.newBuilder()
.addContents(
Content.newBuilder()
.setElement(
Element.newBuilder()
.setElementList(
ElementList.newBuilder()
.addContents(
Content.newBuilder()
.setElement(
Element.newBuilder().setGridRow(gridRow))))))
.build();
EventLogger mockEventLogger = mock(EventLogger.class);
FrameAdapterImpl frameAdapter =
new FrameAdapterImpl(
context, adapterParameters, actionHandler, mockEventLogger, DebugBehavior.SILENT);
frameAdapter.bindModel(frame, 0, null, Collections.emptyList());
LinearLayout gridRowView =
((LinearLayout) ((LinearLayout) frameAdapter.getView().getChildAt(0)).getChildAt(0));
assertThat(gridRowView.getChildCount()).isEqualTo(3);
assertThat(gridRowView.getChildAt(0).getLayoutParams().width)
.isEqualTo((int) LayoutUtils.dpToPx(123, context));
assertThat(
((GridRowView.LayoutParams) gridRowView.getChildAt(1).getLayoutParams())
.getIsCollapsible())
.isTrue();
assertThat(gridRowView.getChildAt(2).getLayoutParams().width)
.isEqualTo(LayoutParams.WRAP_CONTENT);
}
@Test
public void testGetStyleIdsStack() {
adapter.createAdapter(
asElement(GridRow.newBuilder().setStyleReferences(GRID_STYLES).build()), frameContext);
assertThat(adapter.getElementStyleIdsStack()).isEqualTo(GRID_STYLES);
}
@Test
public void testCreateViewGroup() {
LinearLayout gridView = GridRowAdapter.createView(context, Suppliers.of(false));
assertThat(gridView.getOrientation()).isEqualTo(LinearLayout.HORIZONTAL);
assertThat(gridView.getLayoutParams().width).isEqualTo(LayoutParams.MATCH_PARENT);
assertThat(gridView.getLayoutParams().height).isEqualTo(LayoutParams.WRAP_CONTENT);
}
@Test
public void testGetModelFromElement() {
GridRow model =
GridRow.newBuilder()
.setStyleReferences(StyleIdsStack.newBuilder().addStyleIds("spacer"))
.build();
Element elementWithModel = Element.newBuilder().setGridRow(model).build();
assertThat(adapter.getModelFromElement(elementWithModel)).isSameAs(model);
Element elementWithWrongModel =
Element.newBuilder().setCustomElement(CustomElement.getDefaultInstance()).build();
assertThatRunnable(() -> adapter.getModelFromElement(elementWithWrongModel))
.throwsAnExceptionOfType(PietFatalException.class)
.that()
.hasMessageThat()
.contains("Missing GridRow");
Element emptyElement = Element.getDefaultInstance();
assertThatRunnable(() -> adapter.getModelFromElement(emptyElement))
.throwsAnExceptionOfType(PietFatalException.class)
.that()
.hasMessageThat()
.contains("Missing GridRow");
}
private static Element asElement(GridRow gridRow) {
return Element.newBuilder().setGridRow(gridRow).build();
}
}