blob: f7b068820bb8d762538c37c5113210e5690a13ba [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.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
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.View;
import android.widget.LinearLayout;
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.host.AssetProvider;
import com.google.search.now.ui.piet.BindingRefsProto.ElementBindingRef;
import com.google.search.now.ui.piet.BindingRefsProto.TemplateBindingRef;
import com.google.search.now.ui.piet.ElementsProto.BindingContext;
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.GridRow;
import com.google.search.now.ui.piet.ElementsProto.ImageElement;
import com.google.search.now.ui.piet.ElementsProto.TemplateInvocation;
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.ElementsProto.VisibilityState;
import com.google.search.now.ui.piet.ImagesProto.Image;
import com.google.search.now.ui.piet.PietProto.Template;
import com.google.search.now.ui.piet.StylesProto.StyleIdsStack;
import com.google.search.now.ui.piet.TextProto.ParameterizedText;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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 ElementContainerAdapter}. */
@RunWith(RobolectricTestRunner.class)
public class ElementContainerAdapterTest {
private static final Element DUMMY_BASE_ELEMENT = Element.getDefaultInstance();
private static final Element DEFAULT_ELEMENT =
Element.newBuilder().setElementList(ElementList.newBuilder().build()).build();
private static final Element DEFAULT_BOUND_ELEMENT =
Element.newBuilder().setGridRow(GridRow.getDefaultInstance()).build();
private static final String DEFAULT_ELEMENT_BINDING_ID = "bound-grid-element";
private static final ElementBindingRef DEFAULT_ELEMENT_BINDING =
ElementBindingRef.newBuilder().setBindingId(DEFAULT_ELEMENT_BINDING_ID).build();
private static final BindingValue DEFAULT_ELEMENT_BINDING_VALUE =
BindingValue.newBuilder()
.setBindingId(DEFAULT_ELEMENT_BINDING_ID)
.setElement(DEFAULT_BOUND_ELEMENT)
.build();
private static final String DEFAULT_TEMPLATE_ID = "inline-text-template";
private static final Template DEFAULT_TEMPLATE =
Template.newBuilder()
.setTemplateId(DEFAULT_TEMPLATE_ID)
.setElement(
Element.newBuilder()
.setTextElement(
TextElement.newBuilder()
.setParameterizedText(ParameterizedText.getDefaultInstance())))
.build();
private static final TemplateInvocation DEFAULT_TEMPLATE_INVOCATION =
TemplateInvocation.newBuilder()
.setTemplateId(DEFAULT_TEMPLATE_ID)
.addBindingContexts(
BindingContext.newBuilder()
.addBindingValues(BindingValue.newBuilder().setBindingId("T1")))
.addBindingContexts(
BindingContext.newBuilder()
.addBindingValues(BindingValue.newBuilder().setBindingId("T2")))
.build();
private static final String DEFAULT_BOUND_TEMPLATE_ID = "bound-image-template";
private static final Template DEFAULT_BOUND_TEMPLATE =
Template.newBuilder()
.setTemplateId(DEFAULT_BOUND_TEMPLATE_ID)
.setElement(
Element.newBuilder()
.setImageElement(ImageElement.newBuilder().setImage(Image.getDefaultInstance())))
.build();
private static final TemplateInvocation DEFAULT_BOUND_TEMPLATE_INVOCATION =
TemplateInvocation.newBuilder()
.setTemplateId(DEFAULT_BOUND_TEMPLATE_ID)
.addBindingContexts(
BindingContext.newBuilder()
.addBindingValues(BindingValue.newBuilder().setBindingId("BT1")))
.addBindingContexts(
BindingContext.newBuilder()
.addBindingValues(BindingValue.newBuilder().setBindingId("BT2")))
.addBindingContexts(
BindingContext.newBuilder()
.addBindingValues(BindingValue.newBuilder().setBindingId("BT3")))
.build();
private static final String DEFAULT_TEMPLATE_BINDING_ID = "bind-the-bound-image-template";
private static final TemplateBindingRef DEFAULT_TEMPLATE_BINDING =
TemplateBindingRef.newBuilder().setBindingId(DEFAULT_TEMPLATE_BINDING_ID).build();
private static final BindingValue TEMPLATE_BINDING_VALUE =
BindingValue.newBuilder()
.setBindingId(DEFAULT_TEMPLATE_BINDING_ID)
.setTemplateInvocation(DEFAULT_BOUND_TEMPLATE_INVOCATION)
.build();
@Mock private FrameContext frameContext;
@Mock private HostProviders hostProviders;
@Mock private AssetProvider assetProvider;
private Context context;
private TestElementContainerAdapter adapter;
@Before
public void setUp() {
initMocks(this);
context = Robolectric.buildActivity(Activity.class).get();
when(hostProviders.getAssetProvider()).thenReturn(assetProvider);
AdapterParameters adapterParameters =
new AdapterParameters(
context, Suppliers.of(null), hostProviders, new FakeClock(), false, false);
when(frameContext.createTemplateContext(eq(DEFAULT_TEMPLATE), any(BindingContext.class)))
.thenReturn(frameContext);
when(frameContext.createTemplateContext(eq(DEFAULT_BOUND_TEMPLATE), any(BindingContext.class)))
.thenReturn(frameContext);
when(frameContext.makeStyleFor(any(StyleIdsStack.class)))
.thenReturn(adapterParameters.defaultStyleProvider);
when(frameContext.filterImageSourcesByMediaQueryCondition(any(Image.class)))
.thenAnswer(invocation -> invocation.getArguments()[0]);
// Default no-errors behavior
when(frameContext.getTemplateInvocationBindingValue(DEFAULT_TEMPLATE_BINDING))
.thenReturn(TEMPLATE_BINDING_VALUE);
when(frameContext.getTemplate(DEFAULT_TEMPLATE_ID)).thenReturn(DEFAULT_TEMPLATE);
when(frameContext.getTemplate(DEFAULT_BOUND_TEMPLATE_ID)).thenReturn(DEFAULT_BOUND_TEMPLATE);
when(frameContext.getElementBindingValue(DEFAULT_ELEMENT_BINDING))
.thenReturn(DEFAULT_ELEMENT_BINDING_VALUE);
LinearLayout view = new LinearLayout(context);
adapter = new TestElementContainerAdapter(context, adapterParameters, view);
}
@Test
public void testCreateChildAdapters_elementCreatesOneAdapter() {
adapter.createAdapter(
Collections.singletonList(Content.newBuilder().setElement(DEFAULT_ELEMENT).build()),
DUMMY_BASE_ELEMENT,
frameContext);
// A single adapter created from the Element.
assertThat(adapter.childAdapters).hasSize(1);
assertThat(adapter.childAdapters.get(0)).isInstanceOf(ElementListAdapter.class);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(1);
assertThat(adapter.getBaseView().getChildAt(0))
.isSameInstanceAs(adapter.childAdapters.get(0).getView());
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {1});
}
@Test
public void testCreateChildAdapters_templateCreatesMultipleAdapters() {
adapter.createAdapter(
Collections.singletonList(
Content.newBuilder().setTemplateInvocation(DEFAULT_TEMPLATE_INVOCATION).build()),
DUMMY_BASE_ELEMENT,
frameContext);
// One TemplateInstanceAdapter for each BindingContext in the TemplateInvocation.
assertThat(DEFAULT_TEMPLATE_INVOCATION.getBindingContextsCount()).isEqualTo(2);
assertThat(adapter.childAdapters).hasSize(2);
assertThat(adapter.childAdapters.get(0)).isInstanceOf(TextElementAdapter.class);
assertThat(adapter.childAdapters.get(1)).isInstanceOf(TextElementAdapter.class);
verify(frameContext)
.createTemplateContext(DEFAULT_TEMPLATE, DEFAULT_TEMPLATE_INVOCATION.getBindingContexts(0));
verify(frameContext)
.createTemplateContext(DEFAULT_TEMPLATE, DEFAULT_TEMPLATE_INVOCATION.getBindingContexts(1));
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(2);
assertThat(adapter.getBaseView().getChildAt(0))
.isSameInstanceAs(adapter.childAdapters.get(0).getView());
assertThat(adapter.getBaseView().getChildAt(1))
.isSameInstanceAs(adapter.childAdapters.get(1).getView());
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {2});
}
@Test
public void testCreateChildAdapters_templateNotFound() {
when(frameContext.getTemplate(DEFAULT_TEMPLATE_ID)).thenReturn(null);
adapter.createAdapter(
Collections.singletonList(
Content.newBuilder().setTemplateInvocation(DEFAULT_TEMPLATE_INVOCATION).build()),
DUMMY_BASE_ELEMENT,
frameContext);
// No child adapters are created.
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testCreateChildAdapters_boundElement_doesNothing() {
adapter.createAdapter(
Collections.singletonList(
Content.newBuilder().setBoundElement(DEFAULT_ELEMENT_BINDING).build()),
DUMMY_BASE_ELEMENT,
frameContext);
// No child adapters are created.
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testCreateChildAdapters_boundElementNotFound_doesNothing() {
when(frameContext.getElementBindingValue(DEFAULT_ELEMENT_BINDING))
.thenReturn(BindingValue.getDefaultInstance());
adapter.createAdapter(
Collections.singletonList(
Content.newBuilder().setBoundElement(DEFAULT_ELEMENT_BINDING).build()),
DUMMY_BASE_ELEMENT,
frameContext);
// No child adapters are created.
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testCreateChildAdapters_boundTemplate_doesNothing() {
adapter.createAdapter(
Collections.singletonList(
Content.newBuilder().setTemplateBinding(DEFAULT_TEMPLATE_BINDING).build()),
DUMMY_BASE_ELEMENT,
frameContext);
// No child adapters are created.
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testCreateChildAdapters_boundTemplateNotFound_doesNothing() {
when(frameContext.getTemplateInvocationBindingValue(DEFAULT_TEMPLATE_BINDING))
.thenReturn(BindingValue.getDefaultInstance());
adapter.createAdapter(
Collections.singletonList(
Content.newBuilder().setTemplateBinding(DEFAULT_TEMPLATE_BINDING).build()),
DUMMY_BASE_ELEMENT,
frameContext);
// No child adapters are created.
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testCreateChildAdapters_boundTemplateBetweenInlineContents() {
List<Content> contents = new ArrayList<>();
contents.add(Content.newBuilder().setTemplateInvocation(DEFAULT_TEMPLATE_INVOCATION).build());
contents.add(Content.newBuilder().setTemplateBinding(DEFAULT_TEMPLATE_BINDING).build());
contents.add(Content.newBuilder().setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
// Adapters created: 2 templates, (nothing for the binding), 1 element list
assertThat(adapter.childAdapters).hasSize(3);
assertThat(adapter.childAdapters.get(0)).isInstanceOf(TextElementAdapter.class);
assertThat(adapter.childAdapters.get(1)).isInstanceOf(TextElementAdapter.class);
assertThat(adapter.childAdapters.get(2)).isInstanceOf(ElementListAdapter.class);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(3);
for (int i = 0; i < 3; i++) {
assertThat(adapter.getBaseView().getChildAt(i))
.isSameInstanceAs(adapter.childAdapters.get(i).getView());
}
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {2, 0, 1});
}
@Test
public void testCreateChildAdapters_failsIfAdapterNotReleased() {
adapter.createAdapter(
Collections.singletonList(Content.newBuilder().setElement(DEFAULT_ELEMENT).build()),
DUMMY_BASE_ELEMENT,
frameContext);
assertThatRunnable(
() ->
adapter.createAdapter(
Collections.singletonList(
Content.newBuilder().setElement(DEFAULT_ELEMENT).build()),
DUMMY_BASE_ELEMENT,
frameContext))
.throwsAnExceptionOfType(IllegalStateException.class)
.that()
.hasMessageThat()
.contains("release adapter before creating");
}
@Test
public void testBindChildAdapters_createsAdapterIfNotAlreadyCreated() {
adapter.bindModel(
Collections.singletonList(Content.newBuilder().setElement(DEFAULT_ELEMENT).build()),
DUMMY_BASE_ELEMENT,
frameContext);
assertThat(adapter.childAdapters).hasSize(1);
assertThat(adapter.childAdapters.get(0)).isInstanceOf(ElementListAdapter.class);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(1);
}
@Test
public void testBindChildAdapters_element() {
List<Content> contents =
Collections.singletonList(Content.newBuilder().setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
ElementAdapter<? extends View, ?> mockChildAdapter = mock(ElementListAdapter.class);
adapter.childAdapters.set(0, mockChildAdapter);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
verify(mockChildAdapter).bindModel(DEFAULT_ELEMENT, frameContext);
}
@Test
public void testBindChildAdapters_template() {
List<Content> contents =
Collections.singletonList(
Content.newBuilder().setTemplateInvocation(DEFAULT_TEMPLATE_INVOCATION).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
RecyclerKey key1 = adapter.childAdapters.get(0).getKey();
RecyclerKey key2 = adapter.childAdapters.get(1).getKey();
TextElementAdapter mockChildAdapter1 = mock(TextElementAdapter.class);
TextElementAdapter mockChildAdapter2 = mock(TextElementAdapter.class);
when(mockChildAdapter1.getKey()).thenReturn(key1);
when(mockChildAdapter2.getKey()).thenReturn(key2);
adapter.childAdapters.set(0, mockChildAdapter1);
adapter.childAdapters.set(1, mockChildAdapter2);
FrameContext templateContext1 = mock(FrameContext.class);
FrameContext templateContext2 = mock(FrameContext.class);
when(frameContext.createTemplateContext(
DEFAULT_TEMPLATE, DEFAULT_TEMPLATE_INVOCATION.getBindingContexts(0)))
.thenReturn(templateContext1);
when(frameContext.createTemplateContext(
DEFAULT_TEMPLATE, DEFAULT_TEMPLATE_INVOCATION.getBindingContexts(1)))
.thenReturn(templateContext2);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
verify(mockChildAdapter1).bindModel(DEFAULT_TEMPLATE.getElement(), templateContext1);
verify(mockChildAdapter2).bindModel(DEFAULT_TEMPLATE.getElement(), templateContext2);
}
@Test
public void testBindChildAdapters_templateNotFound() {
when(frameContext.getTemplate(DEFAULT_TEMPLATE_ID)).thenReturn(null);
List<Content> contents =
Collections.singletonList(
Content.newBuilder().setTemplateInvocation(DEFAULT_TEMPLATE_INVOCATION).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
// No child adapters are created; binding does nothing.
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testBindChildAdapters_boundElement() {
List<Content> contents =
Collections.singletonList(
Content.newBuilder().setBoundElement(DEFAULT_ELEMENT_BINDING).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(1);
assertThat(adapter.getBaseView().getChildAt(0))
.isSameInstanceAs(adapter.childAdapters.get(0).getView());
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {1});
}
@Test
public void testBindChildAdapters_boundTemplate() {
List<Content> contents =
Collections.singletonList(
Content.newBuilder().setTemplateBinding(DEFAULT_TEMPLATE_BINDING).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(3);
assertThat(adapter.getBaseView().getChildAt(0))
.isSameInstanceAs(adapter.childAdapters.get(0).getView());
assertThat(adapter.getBaseView().getChildAt(1))
.isSameInstanceAs(adapter.childAdapters.get(1).getView());
assertThat(adapter.getBaseView().getChildAt(2))
.isSameInstanceAs(adapter.childAdapters.get(2).getView());
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {3});
}
@Test
public void testBindChildAdapters_boundTemplateNotFound_doesNothing() {
when(frameContext.getTemplate(DEFAULT_BOUND_TEMPLATE_ID)).thenReturn(null);
List<Content> contents =
Collections.singletonList(
Content.newBuilder().setTemplateBinding(DEFAULT_TEMPLATE_BINDING).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testBindChildAdapters_boundTemplateBindingNotFound_doesNothing() {
when(frameContext.getTemplateInvocationBindingValue(DEFAULT_TEMPLATE_BINDING))
.thenReturn(BindingValue.getDefaultInstance());
List<Content> contents =
Collections.singletonList(
Content.newBuilder().setTemplateBinding(DEFAULT_TEMPLATE_BINDING).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testBindChildAdapters_boundTemplateBetweenInlineContents() {
List<Content> contents = new ArrayList<>();
contents.add(Content.newBuilder().setTemplateInvocation(DEFAULT_TEMPLATE_INVOCATION).build());
contents.add(Content.newBuilder().setTemplateBinding(DEFAULT_TEMPLATE_BINDING).build());
contents.add(Content.newBuilder().setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
// 2 templates, 3 templates, 1 element list
assertThat(adapter.childAdapters).hasSize(6);
assertThat(adapter.childAdapters.get(0)).isInstanceOf(TextElementAdapter.class);
assertThat(adapter.childAdapters.get(1)).isInstanceOf(TextElementAdapter.class);
assertThat(adapter.childAdapters.get(2)).isInstanceOf(ImageElementAdapter.class);
assertThat(adapter.childAdapters.get(3)).isInstanceOf(ImageElementAdapter.class);
assertThat(adapter.childAdapters.get(4)).isInstanceOf(ImageElementAdapter.class);
assertThat(adapter.childAdapters.get(5)).isInstanceOf(ElementListAdapter.class);
// Each of these occur 2 times; once for create and once for bind.
verify(frameContext, times(2))
.createTemplateContext(DEFAULT_TEMPLATE, DEFAULT_TEMPLATE_INVOCATION.getBindingContexts(0));
verify(frameContext, times(2))
.createTemplateContext(DEFAULT_TEMPLATE, DEFAULT_TEMPLATE_INVOCATION.getBindingContexts(1));
// These only occur once because we do create and bind at the same time.
verify(frameContext)
.createTemplateContext(
DEFAULT_BOUND_TEMPLATE, DEFAULT_BOUND_TEMPLATE_INVOCATION.getBindingContexts(0));
verify(frameContext)
.createTemplateContext(
DEFAULT_BOUND_TEMPLATE, DEFAULT_BOUND_TEMPLATE_INVOCATION.getBindingContexts(1));
verify(frameContext)
.createTemplateContext(
DEFAULT_BOUND_TEMPLATE, DEFAULT_BOUND_TEMPLATE_INVOCATION.getBindingContexts(2));
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(6);
for (int i = 0; i < 6; i++) {
assertThat(adapter.getBaseView().getChildAt(i))
.isSameInstanceAs(adapter.childAdapters.get(i).getView());
}
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {2, 3, 1});
}
@Test
public void testUnbindChildAdapters_inline() {
List<Content> contents = new ArrayList<>();
contents.add(Content.newBuilder().setTemplateInvocation(DEFAULT_TEMPLATE_INVOCATION).build());
contents.add(Content.newBuilder().setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.unbindModel();
// 2 templates, 1 element list
assertThat(adapter.childAdapters).hasSize(3);
assertThat(adapter.childAdapters.get(0)).isInstanceOf(TextElementAdapter.class);
assertThat(adapter.childAdapters.get(1)).isInstanceOf(TextElementAdapter.class);
assertThat(adapter.childAdapters.get(2)).isInstanceOf(ElementListAdapter.class);
assertThat(adapter.childAdapters.get(0).getRawModel()).isNull();
assertThat(adapter.childAdapters.get(1).getRawModel()).isNull();
assertThat(adapter.childAdapters.get(2).getRawModel()).isNull();
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(3);
for (int i = 0; i < 3; i++) {
assertThat(adapter.getBaseView().getChildAt(i))
.isSameInstanceAs(adapter.childAdapters.get(i).getView());
}
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {2, 1});
}
@Test
public void testUnbindChildAdapters_boundElement() {
List<Content> contents =
Collections.singletonList(
Content.newBuilder().setBoundElement(DEFAULT_ELEMENT_BINDING).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.unbindModel();
// All (bound) adapters and views have been destroyed
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testUnbindChildAdapters_boundTemplate() {
List<Content> contents =
Collections.singletonList(
Content.newBuilder().setTemplateBinding(DEFAULT_TEMPLATE_BINDING).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.unbindModel();
// All (bound) adapters and views have been destroyed
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {0});
}
@Test
public void testUnbindChildAdapters_boundContentBetweenInlineContents() {
List<Content> contents = new ArrayList<>();
contents.add(Content.newBuilder().setTemplateInvocation(DEFAULT_TEMPLATE_INVOCATION).build());
contents.add(Content.newBuilder().setTemplateBinding(DEFAULT_TEMPLATE_BINDING).build());
contents.add(Content.newBuilder().setElement(DEFAULT_ELEMENT).build());
adapter.createAdapter(contents, DUMMY_BASE_ELEMENT, frameContext);
adapter.bindModel(contents, DUMMY_BASE_ELEMENT, frameContext);
assertThat(adapter.childAdapters).hasSize(6);
ImageElementAdapter mockTemplateAdapter = mock(ImageElementAdapter.class);
adapter.childAdapters.set(3, mockTemplateAdapter);
adapter.unbindModel();
// The inline adapters have been unbound; the bound adapters have been destroyed.
assertThat(adapter.childAdapters).hasSize(3);
assertThat(adapter.childAdapters.get(0).getRawModel()).isNull();
assertThat(adapter.childAdapters.get(1).getRawModel()).isNull();
assertThat(adapter.childAdapters.get(2).getRawModel()).isNull();
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(3);
for (int i = 0; i < 3; i++) {
assertThat(adapter.getBaseView().getChildAt(i))
.isSameInstanceAs(adapter.childAdapters.get(i).getView());
}
assertThat(adapter.adaptersPerContent).isEqualTo(new int[] {2, 0, 1});
verify(mockTemplateAdapter).unbindModel();
verify(mockTemplateAdapter).releaseAdapter();
}
@Test
public void testUnbind_visibilityWasGone() {
List<Content> contents = new ArrayList<>();
contents.add(Content.newBuilder().setTemplateInvocation(DEFAULT_TEMPLATE_INVOCATION).build());
contents.add(Content.newBuilder().setElement(DEFAULT_ELEMENT).build());
Element goneElement =
Element.newBuilder()
.setVisibilityState(VisibilityState.newBuilder().setDefaultVisibility(Visibility.GONE))
.build();
adapter.createAdapter(contents, goneElement, frameContext);
adapter.bindModel(contents, goneElement, frameContext);
// Nothing was created because the visibility was GONE
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.adaptersPerContent).isEmpty();
adapter.unbindModel();
// Nothing crashes, and the adapter is still uncreated.
assertThat(adapter.childAdapters).isEmpty();
assertThat(adapter.adaptersPerContent).isEmpty();
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
}
private static class TestElementContainerAdapter
extends ElementContainerAdapter<LinearLayout, List<Content>> {
TestElementContainerAdapter(
Context context, AdapterParameters adapterParameters, LinearLayout view) {
super(context, adapterParameters, view);
}
@Override
List<Content> getContentsFromModel(List<Content> model) {
return model;
}
@Override
List<Content> getModelFromElement(Element baseElement) {
throw new UnsupportedOperationException("Not used in this test");
}
@Override
StyleIdsStack getSubElementStyleIdsStack() {
return StyleIdsStack.getDefaultInstance();
}
}
}