blob: 66c83b9a2f92d604f87b3c0302089297c16bf12b [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.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
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.View;
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.CustomElementAdapter.KeySupplier;
import com.google.android.libraries.feed.piet.host.AssetProvider;
import com.google.android.libraries.feed.piet.host.CustomElementProvider;
import com.google.search.now.ui.piet.BindingRefsProto.CustomBindingRef;
import com.google.search.now.ui.piet.ElementsProto.BindingValue;
import com.google.search.now.ui.piet.ElementsProto.CustomElement;
import com.google.search.now.ui.piet.ElementsProto.CustomElementData;
import com.google.search.now.ui.piet.ElementsProto.Element;
import com.google.search.now.ui.piet.ElementsProto.TextElement;
import com.google.search.now.ui.piet.StylesProto.StyleIdsStack;
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 CustomElementAdapter}. */
@RunWith(RobolectricTestRunner.class)
public class CustomElementAdapterTest {
private static final CustomElementData DUMMY_DATA = CustomElementData.getDefaultInstance();
private static final Element DEFAULT_MODEL =
asElement(CustomElement.newBuilder().setCustomElementData(DUMMY_DATA).build());
@Mock private FrameContext frameContext;
@Mock private CustomElementProvider customElementProvider;
@Mock private HostProviders hostProviders;
@Mock private AssetProvider assetProvider;
private Context context;
private View customView;
private CustomElementAdapter adapter;
private AdapterParameters adapterParameters;
@Before
public void setUp() throws Exception {
initMocks(this);
context = Robolectric.buildActivity(Activity.class).get();
customView = new View(context);
when(hostProviders.getCustomElementProvider()).thenReturn(customElementProvider);
when(hostProviders.getAssetProvider()).thenReturn(assetProvider);
when(assetProvider.isRtL()).thenReturn(false);
when(customElementProvider.createCustomElement(DUMMY_DATA)).thenReturn(customView);
when(frameContext.reportMessage(anyInt(), any(), anyString()))
.thenAnswer(invocation -> invocation.getArguments()[2]);
adapterParameters =
new AdapterParameters(
context, Suppliers.of(null), hostProviders, new FakeClock(), false, false);
when(frameContext.makeStyleFor(any(StyleIdsStack.class)))
.thenReturn(adapterParameters.defaultStyleProvider);
adapter = new KeySupplier().getAdapter(context, adapterParameters);
}
@Test
public void testCreate() {
assertThat(adapter).isNotNull();
}
@Test
public void testCreateAdapter_initializes() {
adapter.createAdapter(DEFAULT_MODEL, frameContext);
assertThat(adapter.getView()).isNotNull();
assertThat(adapter.getKey()).isNotNull();
}
@Test
public void testCreateAdapter_ignoresSubsequentCalls() {
adapter.createAdapter(DEFAULT_MODEL, frameContext);
View adapterView = adapter.getView();
RecyclerKey adapterKey = adapter.getKey();
adapter.createAdapter(DEFAULT_MODEL, frameContext);
assertThat(adapter.getView()).isSameInstanceAs(adapterView);
assertThat(adapter.getKey()).isSameInstanceAs(adapterKey);
}
@Test
public void testBindModel_data() {
adapter.createAdapter(DEFAULT_MODEL, frameContext);
adapter.bindModel(DEFAULT_MODEL, frameContext);
assertThat(adapter.getBaseView().getChildAt(0)).isEqualTo(customView);
}
@Test
public void testBindModel_binding() {
CustomBindingRef bindingRef = CustomBindingRef.newBuilder().setBindingId("CUSTOM!").build();
when(frameContext.getCustomElementBindingValue(bindingRef))
.thenReturn(BindingValue.newBuilder().setCustomElementData(DUMMY_DATA).build());
Element model = asElement(CustomElement.newBuilder().setCustomBinding(bindingRef).build());
adapter.createAdapter(model, frameContext);
adapter.bindModel(model, frameContext);
assertThat(adapter.getBaseView().getChildAt(0)).isEqualTo(customView);
}
@Test
public void testBindModel_noContent() {
adapter.createAdapter(DEFAULT_MODEL, frameContext);
adapter.bindModel(asElement(CustomElement.getDefaultInstance()), frameContext);
verifyZeroInteractions(customElementProvider);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
}
@Test
public void testBindModel_optionalAbsent() {
CustomBindingRef bindingRef =
CustomBindingRef.newBuilder().setBindingId("CUSTOM!").setIsOptional(true).build();
when(frameContext.getCustomElementBindingValue(bindingRef))
.thenReturn(BindingValue.getDefaultInstance());
Element model = asElement(CustomElement.newBuilder().setCustomBinding(bindingRef).build());
adapter.createAdapter(model, frameContext);
// This should not fail.
adapter.bindModel(model, frameContext);
assertThat(adapter.getView().getVisibility()).isEqualTo(View.GONE);
}
@Test
public void testBindModel_noContentInBinding() {
CustomBindingRef bindingRef = CustomBindingRef.newBuilder().setBindingId("CUSTOM").build();
when(frameContext.getCustomElementBindingValue(bindingRef))
.thenReturn(BindingValue.newBuilder().setBindingId("CUSTOM").build());
Element model = asElement(CustomElement.newBuilder().setCustomBinding(bindingRef).build());
adapter.createAdapter(model, frameContext);
assertThatRunnable(() -> adapter.bindModel(model, frameContext))
.throwsAnExceptionOfType(PietFatalException.class)
.that()
.hasMessageThat()
.contains("Custom element binding CUSTOM had no content");
}
@Test
public void testUnbindModel() {
adapter.createAdapter(DEFAULT_MODEL, frameContext);
adapter.bindModel(DEFAULT_MODEL, frameContext);
adapter.unbindModel();
verify(customElementProvider).releaseCustomView(customView, DUMMY_DATA);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
}
@Test
public void testUnbindModel_noChildren() {
adapter.createAdapter(DEFAULT_MODEL, frameContext);
adapter.unbindModel();
verifyZeroInteractions(customElementProvider);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
}
@Test
public void testUnbindModel_multipleChildren() {
View customView2 = new View(context);
when(customElementProvider.createCustomElement(DUMMY_DATA))
.thenReturn(customView)
.thenReturn(customView2);
adapter.createAdapter(DEFAULT_MODEL, frameContext);
adapter.bindModel(DEFAULT_MODEL, frameContext);
adapter.bindModel(DEFAULT_MODEL, frameContext);
adapter.unbindModel();
verify(customElementProvider).releaseCustomView(customView, DUMMY_DATA);
verify(customElementProvider).releaseCustomView(customView2, DUMMY_DATA);
assertThat(adapter.getBaseView().getChildCount()).isEqualTo(0);
}
@Test
public void testGetModelFromElement() {
CustomElement model =
CustomElement.newBuilder()
.setStyleReferences(StyleIdsStack.newBuilder().addStyleIds("custom"))
.build();
Element elementWithModel = Element.newBuilder().setCustomElement(model).build();
assertThat(adapter.getModelFromElement(elementWithModel)).isSameInstanceAs(model);
Element elementWithWrongModel =
Element.newBuilder().setTextElement(TextElement.getDefaultInstance()).build();
assertThatRunnable(() -> adapter.getModelFromElement(elementWithWrongModel))
.throwsAnExceptionOfType(PietFatalException.class)
.that()
.hasMessageThat()
.contains("Missing CustomElement");
Element emptyElement = Element.getDefaultInstance();
assertThatRunnable(() -> adapter.getModelFromElement(emptyElement))
.throwsAnExceptionOfType(PietFatalException.class)
.that()
.hasMessageThat()
.contains("Missing CustomElement");
}
private static Element asElement(CustomElement customElement) {
return Element.newBuilder().setCustomElement(customElement).build();
}
}