blob: 9049f3fbbca40a32f0b839a68dc46f22d5bf6a42 [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.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
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.graphics.Color;
import android.graphics.Typeface;
import android.os.Build.VERSION_CODES;
import android.text.Layout;
import android.text.TextUtils.TruncateAt;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import com.google.android.libraries.feed.common.functional.Consumer;
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.piet.DebugLogger.MessageType;
import com.google.android.libraries.feed.piet.TextElementAdapter.TextElementKey;
import com.google.android.libraries.feed.piet.host.AssetProvider;
import com.google.android.libraries.feed.piet.host.TypefaceProvider;
import com.google.android.libraries.feed.piet.host.TypefaceProvider.GoogleSansTypeface;
import com.google.search.now.ui.piet.BindingRefsProto.StyleBindingRef;
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.TextElement;
import com.google.search.now.ui.piet.ErrorsProto.ErrorCode;
import com.google.search.now.ui.piet.RoundedCornersProto.RoundedCorners;
import com.google.search.now.ui.piet.StylesProto;
import com.google.search.now.ui.piet.StylesProto.Font;
import com.google.search.now.ui.piet.StylesProto.Style;
import com.google.search.now.ui.piet.StylesProto.StyleIdsStack;
import com.google.search.now.ui.piet.StylesProto.TextAlignmentHorizontal;
import com.google.search.now.ui.piet.StylesProto.TextAlignmentVertical;
import com.google.search.now.ui.piet.StylesProto.Typeface.CommonTypeface;
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.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/** Tests of the {@link TextElementAdapter}. */
@RunWith(RobolectricTestRunner.class)
public class TextElementAdapterTest {
@Mock private FrameContext frameContext;
@Mock private StyleProvider mockStyleProvider;
@Mock private HostProviders mockHostProviders;
@Mock private AssetProvider mockAssetProvider;
@Mock private TypefaceProvider mockTypefaceProvider;
private AdapterParameters adapterParameters;
private Context context;
private TextElementAdapter adapter;
private int emptyTextElementLineHeight;
@Before
public void setUp() {
initMocks(this);
context = Robolectric.buildActivity(Activity.class).get();
when(mockHostProviders.getAssetProvider()).thenReturn(mockAssetProvider);
when(mockAssetProvider.isRtL()).thenReturn(false);
when(mockStyleProvider.getRoundedCorners()).thenReturn(RoundedCorners.getDefaultInstance());
when(mockStyleProvider.getTextAlignment()).thenReturn(Gravity.START | Gravity.TOP);
adapterParameters =
new AdapterParameters(
null,
null,
mockHostProviders,
new ParameterizedTextEvaluator(new FakeClock()),
null,
null,
new FakeClock());
TextElementAdapter adapterForEmptyElement =
new TestTextElementAdapter(context, adapterParameters);
// Get emptyTextElementHeight based on a text element with no content or styles set.
Element textElement = getBaseTextElement();
adapterForEmptyElement.createAdapter(textElement, frameContext);
emptyTextElementLineHeight = adapterForEmptyElement.getBaseView().getLineHeight();
adapter = new TestTextElementAdapter(context, adapterParameters);
}
@Test
public void testHyphenationDisabled() {
assertThat(adapter.getBaseView().getBreakStrategy()).isEqualTo(Layout.BREAK_STRATEGY_SIMPLE);
}
@Test
public void testCreateAdapter_setsStyles() {
Element textElement = getBaseTextElement(mockStyleProvider);
int color = Color.RED;
int maxLines = 72;
when(mockStyleProvider.getFont()).thenReturn(Font.getDefaultInstance());
when(mockStyleProvider.getColor()).thenReturn(color);
when(mockStyleProvider.getMaxLines()).thenReturn(maxLines);
adapter.createAdapter(textElement, frameContext);
verify(mockStyleProvider).applyElementStyles(adapter);
assertThat(adapter.getBaseView().getMaxLines()).isEqualTo(maxLines);
assertThat(adapter.getBaseView().getEllipsize()).isEqualTo(TruncateAt.END);
assertThat(adapter.getBaseView().getCurrentTextColor()).isEqualTo(color);
}
@Test
public void testSetFont_usesCommonFont() {
Font font =
Font.newBuilder()
.addTypeface(
StylesProto.Typeface.newBuilder()
.setCommonTypeface(CommonTypeface.PLATFORM_DEFAULT_MEDIUM))
.addTypeface(StylesProto.Typeface.newBuilder().setCustomTypeface("notused"))
.build();
TextElementKey key = new TextElementKey(font);
adapter.setValuesUsedInRecyclerKey(key, frameContext);
verify(mockAssetProvider, never())
.getTypeface(anyString(), anyBoolean(), ArgumentMatchers.<Consumer<Typeface>>any());
}
@Test
public void testSetFont_callsHost() {
Font font =
Font.newBuilder()
.addTypeface(StylesProto.Typeface.newBuilder().setCustomTypeface("goodfont"))
.build();
TextElementKey key = new TextElementKey(font);
adapter.setValuesUsedInRecyclerKey(key, frameContext);
verify(mockAssetProvider, atLeastOnce())
.getTypeface(eq("goodfont"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
}
@Test
public void testSetFont_callsHostWithItalic() {
Font font =
Font.newBuilder()
.addTypeface(StylesProto.Typeface.newBuilder().setCustomTypeface("goodfont"))
.addTypeface(StylesProto.Typeface.newBuilder().setCustomTypeface("badfont"))
.setItalic(true)
.build();
TextElementKey key = new TextElementKey(font);
adapter.setValuesUsedInRecyclerKey(key, frameContext);
verify(mockAssetProvider, atLeastOnce())
.getTypeface(eq("goodfont"), eq(true), ArgumentMatchers.<Consumer<Typeface>>any());
}
@Test
public void testSetFont_callsHostWithFallback() {
Font font =
Font.newBuilder()
.addTypeface(StylesProto.Typeface.newBuilder().setCustomTypeface("badfont"))
.addTypeface(StylesProto.Typeface.newBuilder().setCustomTypeface("goodfont"))
.build();
TextElementKey key = new TextElementKey(font);
// Consumer accepts null for badfont
doAnswer(
answer -> {
Consumer<Typeface> typefaceConsumer = answer.getArgument(2);
typefaceConsumer.accept(null);
return null;
})
.when(mockAssetProvider)
.getTypeface(eq("badfont"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
// Consumer accepts hosttypeface for goodfont
Typeface hostTypeface = Typeface.create("host", Typeface.BOLD_ITALIC);
doAnswer(
answer -> {
Consumer<Typeface> typefaceConsumer = answer.getArgument(2);
typefaceConsumer.accept(hostTypeface);
return null;
})
.when(mockAssetProvider)
.getTypeface(eq("goodfont"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
adapter.setValuesUsedInRecyclerKey(key, frameContext);
Typeface typeface = adapter.getBaseView().getTypeface();
assertThat(typeface).isEqualTo(hostTypeface);
InOrder inOrder = inOrder(mockAssetProvider);
inOrder
.verify(mockAssetProvider, atLeastOnce())
.getTypeface(eq("badfont"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
inOrder
.verify(mockAssetProvider, atLeastOnce())
.getTypeface(eq("goodfont"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
}
// Same test as above, but on JB. Catches bugs that might only appear on older versions, like
// getting NPEs on getTypeface() (which is now fixed).
@Config(sdk = VERSION_CODES.JELLY_BEAN)
@Test
public void testSetFont_callsHostWithFallback_JB() {
Font font =
Font.newBuilder()
.addTypeface(StylesProto.Typeface.newBuilder().setCustomTypeface("badfont"))
.addTypeface(StylesProto.Typeface.newBuilder().setCustomTypeface("goodfont"))
.build();
TextElementKey key = new TextElementKey(font);
// Consumer accepts null for badfont
doAnswer(
answer -> {
Consumer<Typeface> typefaceConsumer = answer.getArgument(2);
typefaceConsumer.accept(null);
return null;
})
.when(mockAssetProvider)
.getTypeface(eq("badfont"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
// Consumer accepts hosttypeface for goodfont
Typeface hostTypeface = Typeface.create("host", Typeface.BOLD_ITALIC);
doAnswer(
answer -> {
Consumer<Typeface> typefaceConsumer = answer.getArgument(2);
typefaceConsumer.accept(hostTypeface);
return null;
})
.when(mockAssetProvider)
.getTypeface(eq("goodfont"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
adapter.setValuesUsedInRecyclerKey(key, frameContext);
Typeface typeface = adapter.getBaseView().getTypeface();
assertThat(typeface).isEqualTo(hostTypeface);
InOrder inOrder = inOrder(mockAssetProvider);
inOrder
.verify(mockAssetProvider, atLeastOnce())
.getTypeface(eq("badfont"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
inOrder
.verify(mockAssetProvider, atLeastOnce())
.getTypeface(eq("goodfont"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
}
@Test
public void testSetFont_hostReturnsNull() {
Font font =
Font.newBuilder()
.addTypeface(StylesProto.Typeface.newBuilder().setCustomTypeface("notvalid"))
.build();
doAnswer(
answer -> {
Consumer<Typeface> typefaceConsumer = answer.getArgument(2);
typefaceConsumer.accept(null);
return null;
})
.when(mockAssetProvider)
.getTypeface(eq("notvalid"), eq(false), ArgumentMatchers.<Consumer<Typeface>>any());
TextElementKey key = new TextElementKey(font);
adapter.setValuesUsedInRecyclerKey(key, frameContext);
Typeface typeface = adapter.getBaseView().getTypeface();
verify(frameContext)
.reportMessage(
MessageType.WARNING,
ErrorCode.ERR_MISSING_FONTS,
"Could not load specified typefaces.");
assertThat(typeface).isEqualTo(new TextView(context).getTypeface());
}
@Test
public void testSetFont_callsHostForGoogleSans() {
Font font =
Font.newBuilder()
.addTypeface(
StylesProto.Typeface.newBuilder()
.setCommonTypeface(CommonTypeface.GOOGLE_SANS_MEDIUM))
.build();
TextElementKey key = new TextElementKey(font);
adapter.setValuesUsedInRecyclerKey(key, frameContext);
verify(mockAssetProvider, atLeastOnce())
.getTypeface(
eq(GoogleSansTypeface.GOOGLE_SANS_MEDIUM),
eq(false),
ArgumentMatchers.<Consumer<Typeface>>any());
}
@Test
public void testSetFont_italics() {
Font font = Font.newBuilder().setItalic(true).build();
TextElementKey key = new TextElementKey(font);
adapter.setValuesUsedInRecyclerKey(key, frameContext);
Typeface typeface = adapter.getBaseView().getTypeface();
// Typeface.isBold and Typeface.isItalic don't work properly in roboelectric.
assertThat(typeface.getStyle() & Typeface.BOLD).isEqualTo(0);
assertThat(typeface.getStyle() & Typeface.ITALIC).isGreaterThan(0);
}
@Test
public void testGoogleSansEnumToStringDef() {
assertThat(TextElementAdapter.googleSansEnumToStringDef(CommonTypeface.GOOGLE_SANS_REGULAR))
.isEqualTo(GoogleSansTypeface.GOOGLE_SANS_REGULAR);
assertThat(TextElementAdapter.googleSansEnumToStringDef(CommonTypeface.GOOGLE_SANS_MEDIUM))
.isEqualTo(GoogleSansTypeface.GOOGLE_SANS_MEDIUM);
assertThat(TextElementAdapter.googleSansEnumToStringDef(CommonTypeface.PLATFORM_DEFAULT_MEDIUM))
.isEqualTo(GoogleSansTypeface.UNDEFINED);
}
@Test
public void testSetLineHeight() {
int lineHeightToSetSp = 18;
Style lineHeightStyle1 =
Style.newBuilder().setFont(Font.newBuilder().setLineHeight(lineHeightToSetSp)).build();
StyleProvider styleProvider1 = new StyleProvider(lineHeightStyle1, mockAssetProvider);
Element textElement = getBaseTextElement(styleProvider1);
adapter.createAdapter(textElement, frameContext);
TextView textView = adapter.getBaseView();
float actualLineHeightPx = textView.getLineHeight();
int actualLineHeightSp = (int) LayoutUtils.pxToSp(actualLineHeightPx, textView.getContext());
assertThat(actualLineHeightSp).isEqualTo(lineHeightToSetSp);
}
@Test
public void testGetExtraLineHeight_roundDown() {
// Extra height is 40.2px. This gets rounded down between the lines (to 40) and rounded up
// for top and bottom padding (for 21 + 20 = 41).
initializeAdapterWithExtraLineHeightPx(40.2f);
TextElementAdapter.ExtraLineHeight extraLineHeight = adapter.getExtraLineHeight();
assertThat(extraLineHeight.betweenLinesExtraPx()).isEqualTo(40);
assertThat(extraLineHeight.bottomPaddingPx()).isEqualTo(21);
assertThat(extraLineHeight.topPaddingPx()).isEqualTo(20);
}
@Config(sdk = VERSION_CODES.KITKAT)
@Test
public void testGetExtraLineHeight_roundDown_kitkat() {
// Extra height is 40.2px. In KitKat and lower, 40 pixels (the amount added between lines)
// will have already been added to the bottom. To get to our desired value of 21 bottom pixels,
// the actual bottom padding must be -19 (40 - 19 = 21).
initializeAdapterWithExtraLineHeightPx(40.2f);
TextElementAdapter.ExtraLineHeight extraLineHeight = adapter.getExtraLineHeight();
assertThat(extraLineHeight.betweenLinesExtraPx()).isEqualTo(40);
assertThat(extraLineHeight.bottomPaddingPx()).isEqualTo(-19);
assertThat(extraLineHeight.topPaddingPx()).isEqualTo(20);
}
@Test
public void testGetExtraLineHeight_noRound() {
// Extra height is 40px. 40 pixels will be added between each line, and that amount is split
// (20 and 20) to be added to the top and bottom of the text element.
initializeAdapterWithExtraLineHeightPx(40.0f);
TextElementAdapter.ExtraLineHeight extraLineHeight = adapter.getExtraLineHeight();
assertThat(extraLineHeight.betweenLinesExtraPx()).isEqualTo(40);
assertThat(extraLineHeight.bottomPaddingPx()).isEqualTo(20);
assertThat(extraLineHeight.topPaddingPx()).isEqualTo(20);
}
@Config(sdk = VERSION_CODES.KITKAT)
@Test
public void testGetExtraLineHeight_noRound_kitkat() {
// Extra height is 40px. In KitKat and lower, 40 pixels (the amount added between lines) will
// have already been added to the bottom. To get to our desired value of 20 bottom pixels, the
// actual bottom padding must be -20 (40 - 20 = 20).
initializeAdapterWithExtraLineHeightPx(40.0f);
TextElementAdapter.ExtraLineHeight extraLineHeight = adapter.getExtraLineHeight();
assertThat(extraLineHeight.betweenLinesExtraPx()).isEqualTo(40);
assertThat(extraLineHeight.bottomPaddingPx()).isEqualTo(-20);
assertThat(extraLineHeight.topPaddingPx()).isEqualTo(20);
}
@Test
public void testGetExtraLineHeight_roundUp() {
// Extra height is 40.8px. This gets rounded up between the lines (to 41) and rounded down
// for top and bottom padding (for 20 + 20 = 40).
initializeAdapterWithExtraLineHeightPx(40.8f);
TextElementAdapter.ExtraLineHeight extraLineHeight = adapter.getExtraLineHeight();
assertThat(extraLineHeight.betweenLinesExtraPx()).isEqualTo(41);
assertThat(extraLineHeight.bottomPaddingPx()).isEqualTo(20);
assertThat(extraLineHeight.topPaddingPx()).isEqualTo(20);
}
@Config(sdk = VERSION_CODES.KITKAT)
@Test
public void testGetExtraLineHeight_roundUp_kitkat() {
// Extra height is 40.8px. In KitKat and lower, 41 pixels (the amount added between lines)
// will have already been added to the bottom. To get to our desired value of 20 bottom pixels,
// the actual bottom padding must be -21 (41 - 21 = 20).
initializeAdapterWithExtraLineHeightPx(40.8f);
TextElementAdapter.ExtraLineHeight extraLineHeight = adapter.getExtraLineHeight();
assertThat(extraLineHeight.betweenLinesExtraPx()).isEqualTo(41);
assertThat(extraLineHeight.bottomPaddingPx()).isEqualTo(-21);
assertThat(extraLineHeight.topPaddingPx()).isEqualTo(20);
}
private void initializeAdapterWithExtraLineHeightPx(float lineHeightPx) {
// Line height is specified in sp, so line height px = scaledDensity x line height sp
// These tests set display density because, in order to test the rounding behavior of
// extraLineHeight, we need a lineHeight integer (in sp) that results in a decimal value in px.
int lineHeightSp = 10;
float totalLineHeightPx = emptyTextElementLineHeight + lineHeightPx;
context.getResources().getDisplayMetrics().scaledDensity = totalLineHeightPx / lineHeightSp;
Style lineHeightStyle1 =
Style.newBuilder().setFont(Font.newBuilder().setLineHeight(lineHeightSp)).build();
StyleProvider styleProvider1 = new StyleProvider(lineHeightStyle1, mockAssetProvider);
Element textElement = getBaseTextElement(styleProvider1);
adapter.createAdapter(textElement, frameContext);
}
@Test
public void testBind_setsTextAlignment_horizontal() {
Style style =
Style.newBuilder()
.setTextAlignmentHorizontal(TextAlignmentHorizontal.TEXT_ALIGNMENT_CENTER)
.build();
StyleProvider styleProvider = new StyleProvider(style, mockAssetProvider);
Element textElement = getBaseTextElement(styleProvider);
adapter.createAdapter(textElement, frameContext);
adapter.bindModel(textElement, frameContext);
assertThat(adapter.getBaseView().getGravity())
.isEqualTo(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
}
@Test
public void testBind_setsTextAlignment_vertical() {
Style style =
Style.newBuilder()
.setTextAlignmentVertical(TextAlignmentVertical.TEXT_ALIGNMENT_BOTTOM)
.build();
StyleProvider styleProvider = new StyleProvider(style, mockAssetProvider);
Element textElement = getBaseTextElement(styleProvider);
adapter.createAdapter(textElement, frameContext);
adapter.bindModel(textElement, frameContext);
assertThat(adapter.getBaseView().getGravity()).isEqualTo(Gravity.START | Gravity.BOTTOM);
}
@Test
public void testBind_setsTextAlignment_both() {
Style style =
Style.newBuilder()
.setTextAlignmentHorizontal(TextAlignmentHorizontal.TEXT_ALIGNMENT_END)
.setTextAlignmentVertical(TextAlignmentVertical.TEXT_ALIGNMENT_MIDDLE)
.build();
StyleProvider styleProvider = new StyleProvider(style, mockAssetProvider);
Element textElement = getBaseTextElement(styleProvider);
adapter.createAdapter(textElement, frameContext);
adapter.bindModel(textElement, frameContext);
assertThat(adapter.getBaseView().getGravity()).isEqualTo(Gravity.END | Gravity.CENTER_VERTICAL);
}
@Test
public void testBind_setsTextAlignment_default() {
Style style = Style.getDefaultInstance();
StyleProvider styleProvider = new StyleProvider(style, mockAssetProvider);
Element textElement = getBaseTextElement(styleProvider);
adapter.getBaseView().setGravity(Gravity.BOTTOM | Gravity.RIGHT);
adapter.createAdapter(textElement, frameContext);
adapter.bindModel(textElement, frameContext);
assertThat(adapter.getBaseView().getGravity()).isEqualTo(Gravity.START | Gravity.TOP);
}
@Test
public void testBind_setsStylesOnlyIfBindingIsDefined() {
int maxLines = 2;
Style style = Style.newBuilder().setMaxLines(maxLines).build();
StyleProvider styleProvider = new StyleProvider(style, mockAssetProvider);
Element textElement = getBaseTextElement(styleProvider);
adapter.createAdapter(textElement, frameContext);
adapter.bindModel(textElement, frameContext);
assertThat(adapter.getBaseView().getMaxLines()).isEqualTo(maxLines);
// Styles should not change on a re-bind
adapter.unbindModel();
StyleIdsStack otherStyle = StyleIdsStack.newBuilder().addStyleIds("ignored").build();
textElement = getBaseTextElement().toBuilder().setStyleReferences(otherStyle).build();
adapter.bindModel(textElement, frameContext);
assertThat(adapter.getBaseView().getMaxLines()).isEqualTo(maxLines);
verify(frameContext, never()).makeStyleFor(otherStyle);
// Styles only change if new model has style bindings
adapter.unbindModel();
StyleIdsStack otherStyleWithBinding =
StyleIdsStack.newBuilder()
.setStyleBinding(StyleBindingRef.newBuilder().setBindingId("prionailurus"))
.build();
textElement =
getBaseTextElement().toBuilder().setStyleReferences(otherStyleWithBinding).build();
adapter.bindModel(textElement, frameContext);
verify(frameContext).makeStyleFor(otherStyleWithBinding);
}
@Test
public void bindWithUpdatedDensity_shouldUpdateLineHeight() {
final int lineHeightInTextElement = 50;
context.getResources().getDisplayMetrics().scaledDensity = 1;
Style style =
Style.newBuilder()
.setFont(Font.newBuilder().setLineHeight(lineHeightInTextElement))
.build();
StyleProvider styleProvider = new StyleProvider(style, mockAssetProvider);
Element textElement = getBaseTextElement(styleProvider);
adapter.createAdapter(textElement, frameContext);
adapter.bindModel(textElement, frameContext);
assertThat(adapter.getBaseView().getLineHeight()).isEqualTo(lineHeightInTextElement);
adapter.unbindModel();
// Change line height by changing the scale density
context.getResources().getDisplayMetrics().scaledDensity = 2;
adapter.bindModel(textElement, frameContext);
// getLineHeight() still returns pixels. The number of pixels should have been updated to
// reflect the new density.
assertThat(adapter.getBaseView().getLineHeight()).isEqualTo(lineHeightInTextElement * 2);
adapter.unbindModel();
// Make sure the line height is updated again when the scale density is changed back.
context.getResources().getDisplayMetrics().scaledDensity = 1;
adapter.bindModel(textElement, frameContext);
assertThat(adapter.getBaseView().getLineHeight()).isEqualTo(lineHeightInTextElement);
}
@Test
public void testUnbind() {
Element textElement = getBaseTextElement(null);
adapter.createAdapter(textElement, frameContext);
adapter.bindModel(textElement, frameContext);
TextView adapterView = adapter.getBaseView();
adapterView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
adapterView.setText("OLD TEXT");
adapter.unbindModel();
assertThat(adapter.getBaseView()).isSameInstanceAs(adapterView);
assertThat(adapterView.getTextAlignment()).isEqualTo(View.TEXT_ALIGNMENT_GRAVITY);
assertThat(adapterView.getText().toString()).isEmpty();
}
@Test
public void testGetStyles() {
StyleIdsStack elementStyles = StyleIdsStack.newBuilder().addStyleIds("hair").build();
when(mockStyleProvider.getFont()).thenReturn(Font.getDefaultInstance());
Element textElement =
getBaseTextElement(mockStyleProvider).toBuilder().setStyleReferences(elementStyles).build();
adapter.createAdapter(textElement, frameContext);
assertThat(adapter.getElementStyleIdsStack()).isSameInstanceAs(elementStyles);
}
@Test
public void testGetModelFromElement() {
TextElement model =
TextElement.newBuilder()
.setParameterizedText(ParameterizedText.newBuilder().setText("text"))
.build();
Element elementWithModel =
Element.newBuilder()
.setStyleReferences(StyleIdsStack.newBuilder().addStyleIds("spacer"))
.setTextElement(model)
.build();
assertThat(adapter.getModelFromElement(elementWithModel)).isSameInstanceAs(elementWithModel);
Element elementWithWrongModel =
Element.newBuilder().setCustomElement(CustomElement.getDefaultInstance()).build();
assertThatRunnable(() -> adapter.getModelFromElement(elementWithWrongModel))
.throwsAnExceptionOfType(PietFatalException.class)
.that()
.hasMessageThat()
.contains("Missing TextElement");
Element emptyElement = Element.getDefaultInstance();
assertThatRunnable(() -> adapter.getModelFromElement(emptyElement))
.throwsAnExceptionOfType(PietFatalException.class)
.that()
.hasMessageThat()
.contains("Missing TextElement");
}
private Element getBaseTextElement() {
return getBaseTextElement(null);
}
private Element getBaseTextElement(/*@Nullable*/ StyleProvider styleProvider) {
StyleProvider sp =
styleProvider != null ? styleProvider : adapterParameters.defaultStyleProvider;
when(frameContext.makeStyleFor(any(StyleIdsStack.class))).thenReturn(sp);
return Element.newBuilder().setTextElement(TextElement.getDefaultInstance()).build();
}
private static class TestTextElementAdapter extends TextElementAdapter {
TestTextElementAdapter(Context context, AdapterParameters parameters) {
super(context, parameters);
}
@Override
void setTextOnView(FrameContext frameContext, TextElement textElement) {}
@Override
TextElementKey createKey(Font font) {
return new TextElementKey(font);
}
}
}