blob: 6a06d5e90d68e5890af0b171b8b2777454c1839f [file] [log] [blame]
package android.content.res;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.util.TypedValue.COMPLEX_UNIT_IN;
import static android.util.TypedValue.COMPLEX_UNIT_MM;
import static android.util.TypedValue.COMPLEX_UNIT_PT;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static android.util.TypedValue.COMPLEX_UNIT_SP;
import static android.util.TypedValue.TYPE_FIRST_COLOR_INT;
import static android.util.TypedValue.TYPE_INT_BOOLEAN;
import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
import static android.util.TypedValue.TYPE_INT_COLOR_RGB8;
import static android.util.TypedValue.TYPE_INT_DEC;
import static android.util.TypedValue.TYPE_LAST_INT;
import static android.util.TypedValue.TYPE_REFERENCE;
import static android.util.TypedValue.TYPE_STRING;
import static android.util.TypedValue.applyDimension;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.robolectric.testapp.R.color.test_ARGB8;
import static org.robolectric.testapp.R.color.test_RGB8;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.graphics.fonts.Font;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Xml;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import com.google.common.collect.Range;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;
import org.robolectric.testapp.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Compatibility test for {@link Resources}
*/
@DoNotInstrument
@RunWith(AndroidJUnit4.class)
public class ResourcesTest {
private Resources resources;
private Context context;
@Before
public void setup() {
context = ApplicationProvider.getApplicationContext();
resources = context.getResources();
}
@Test
public void getString() {
assertThat(resources.getString(R.string.hello)).isEqualTo("Hello");
assertThat(resources.getString(R.string.say_it_with_item)).isEqualTo("flowers");
}
@Test
public void getString_withReference() {
assertThat(resources.getString(R.string.greeting)).isEqualTo("Howdy");
}
@Test
public void getString_withInterpolation() {
assertThat(resources.getString(R.string.interpolate, "value")).isEqualTo("Here is a value!");
}
@Test
public void getString_withHtml() {
assertThat(resources.getString(R.string.some_html, "value")).isEqualTo("Hello, world");
}
@Test
public void getString_withSurroundingQuotes() {
assertThat(resources.getString(R.string.surrounding_quotes, "value")).isEqualTo("This'll work");
}
@Test
public void getStringWithEscapedApostrophes() {
assertThat(resources.getString(R.string.escaped_apostrophe)).isEqualTo("This'll also work");
}
@Test
public void getStringWithEscapedQuotes() {
assertThat(resources.getString(R.string.escaped_quotes)).isEqualTo("Click \"OK\"");
}
@Test
public void getString_StringWithInlinedQuotesAreStripped() {
assertThat(resources.getString(R.string.bad_example)).isEqualTo("This is a bad string.");
}
@Test
public void getStringShouldStripNewLines() {
assertThat(resources.getString(R.string.leading_and_trailing_new_lines)).isEqualTo("Some text");
}
@Test
public void preserveEscapedNewlineAndTab() {
assertThat(resources.getString(R.string.new_lines_and_tabs, 4)).isEqualTo("4\tmph\nfaster");
}
@Test
public void getStringShouldConvertCodePoints() {
assertThat(resources.getString(R.string.non_breaking_space)).isEqualTo("Closing"
+ " soon:\u00A05pm");
assertThat(resources.getString(R.string.space)).isEqualTo("Closing soon: 5pm");
}
@Test
public void getMultilineLayoutResource_shouldResolveLayoutReferencesWithLineBreaks() {
// multiline_layout is a layout reference to activity_main layout.
TypedValue multilineLayoutValue = new TypedValue();
resources.getValue(R.layout.multiline_layout, multilineLayoutValue, true /* resolveRefs */);
TypedValue mainActivityLayoutValue = new TypedValue();
resources.getValue(R.layout.activity_main, mainActivityLayoutValue, false /* resolveRefs */);
assertThat(multilineLayoutValue.string).isEqualTo(mainActivityLayoutValue.string);
}
@Test
public void getText_withHtml() {
assertThat(resources.getText(R.string.some_html, "value").toString()).isEqualTo("Hello, world");
// TODO: Raw resources have lost the tags early, but the following call should return a
// SpannedString
// assertThat(resources.getText(R.string.some_html)).isInstanceOf(SpannedString.class);
}
@Test
public void getText_plainString() {
assertThat(resources.getText(R.string.hello, "value").toString()).isEqualTo("Hello");
assertThat(resources.getText(R.string.hello)).isInstanceOf(String.class);
}
@Test
public void getText_withLayoutId() {
// This isn't _really_ supported by the platform (gives a lint warning that getText() expects a
// String resource type
// but the actual platform behaviour is to return a string that equals
// "res/layout/layout_file.xml" so the current
// Robolectric behaviour deviates from the platform as we append the full file path from the
// current working directory.
String textString = resources.getText(R.layout.different_screen_sizes, "value").toString();
assertThat(textString).containsMatch("/different_screen_sizes.xml$");
// If we run tests on devices with different config, the resource system will select different
// layout directories.
assertThat(textString).containsMatch("^res/layout");
}
@Test
public void getStringArray() {
assertThat(resources.getStringArray(R.array.items)).isEqualTo(new String[]{"foo", "bar"});
assertThat(resources.getStringArray(R.array.greetings))
.isEqualTo(new String[] {"hola", "Hello"});
}
@Test
public void withIdReferenceEntry_obtainTypedArray() {
TypedArray typedArray = resources.obtainTypedArray(R.array.typed_array_with_resource_id);
assertThat(typedArray.length()).isEqualTo(2);
assertThat(typedArray.getResourceId(0, 0)).isEqualTo(R.id.id_declared_in_item_tag);
assertThat(typedArray.getResourceId(1, 0)).isEqualTo(R.id.id_declared_in_layout);
}
@Test
public void obtainTypedArray() {
final TypedArray valuesTypedArray = resources.obtainTypedArray(R.array.typed_array_values);
assertThat(valuesTypedArray.getString(0)).isEqualTo("abcdefg");
assertThat(valuesTypedArray.getInt(1, 0)).isEqualTo(3875);
assertThat(valuesTypedArray.getInteger(1, 0)).isEqualTo(3875);
assertThat(valuesTypedArray.getFloat(2, 0.0f)).isEqualTo(2.0f);
assertThat(valuesTypedArray.getColor(3, Color.BLACK)).isEqualTo(Color.MAGENTA);
assertThat(valuesTypedArray.getColor(4, Color.BLACK)).isEqualTo(Color.parseColor("#00ffff"));
assertThat(valuesTypedArray.getDimension(5, 0.0f))
.isEqualTo(applyDimension(COMPLEX_UNIT_PX, 8, resources.getDisplayMetrics()));
assertThat(valuesTypedArray.getDimension(6, 0.0f))
.isEqualTo(applyDimension(COMPLEX_UNIT_DIP, 12, resources.getDisplayMetrics()));
assertThat(valuesTypedArray.getDimension(7, 0.0f))
.isEqualTo(applyDimension(COMPLEX_UNIT_DIP, 6, resources.getDisplayMetrics()));
assertThat(valuesTypedArray.getDimension(8, 0.0f))
.isEqualTo(applyDimension(COMPLEX_UNIT_MM, 3, resources.getDisplayMetrics()));
assertThat(valuesTypedArray.getDimension(9, 0.0f))
.isEqualTo(applyDimension(COMPLEX_UNIT_IN, 4, resources.getDisplayMetrics()));
assertThat(valuesTypedArray.getDimension(10, 0.0f))
.isEqualTo(applyDimension(COMPLEX_UNIT_SP, 36, resources.getDisplayMetrics()));
assertThat(valuesTypedArray.getDimension(11, 0.0f))
.isEqualTo(applyDimension(COMPLEX_UNIT_PT, 18, resources.getDisplayMetrics()));
final TypedArray refsTypedArray = resources.obtainTypedArray(R.array.typed_array_references);
assertThat(refsTypedArray.getString(0)).isEqualTo("apple");
assertThat(refsTypedArray.getString(1)).isEqualTo("banana");
assertThat(refsTypedArray.getInt(2, 0)).isEqualTo(5);
assertThat(refsTypedArray.getBoolean(3, false)).isTrue();
assertThat(refsTypedArray.getResourceId(8, 0)).isEqualTo(R.array.string_array_values);
assertThat(refsTypedArray.getTextArray(8))
.asList()
.containsAtLeast(
"abcdefg",
"3875",
"2.0",
"#ffff00ff",
"#00ffff",
"8px",
"12dp",
"6dip",
"3mm",
"4in",
"36sp",
"18pt");
assertThat(refsTypedArray.getResourceId(9, 0)).isEqualTo(R.style.Theme_Robolectric);
}
@Test
public void getInt() {
assertThat(resources.getInteger(R.integer.meaning_of_life)).isEqualTo(42);
assertThat(resources.getInteger(R.integer.test_integer1)).isEqualTo(2000);
assertThat(resources.getInteger(R.integer.test_integer2)).isEqualTo(9);
assertThat(resources.getInteger(R.integer.test_large_hex)).isEqualTo(-65536);
assertThat(resources.getInteger(R.integer.test_value_with_zero)).isEqualTo(7210);
assertThat(resources.getInteger(R.integer.meaning_of_life_as_item)).isEqualTo(42);
}
@Test
public void getInt_withReference() {
assertThat(resources.getInteger(R.integer.reference_to_meaning_of_life)).isEqualTo(42);
}
@Test
public void getIntArray() {
assertThat(resources.getIntArray(R.array.empty_int_array)).isEqualTo(new int[]{});
assertThat(resources.getIntArray(R.array.zero_to_four_int_array)).isEqualTo(new int[]{0, 1, 2, 3, 4});
assertThat(resources.getIntArray(R.array.with_references_int_array)).isEqualTo(new int[]{0, 2000, 1});
assertThat(resources.getIntArray(R.array.referenced_colors_int_array)).isEqualTo(new int[]{0x1, 0xFFFFFFFF, 0xFF000000, 0xFFF5F5F5, 0x802C76AD});
}
@Test
public void getBoolean() {
assertThat(resources.getBoolean(R.bool.false_bool_value)).isEqualTo(false);
assertThat(resources.getBoolean(R.bool.true_as_item)).isEqualTo(true);
}
@Test
public void getBoolean_withReference() {
assertThat(resources.getBoolean(R.bool.reference_to_true)).isEqualTo(true);
}
@Test
public void getDimension() {
assertThat(resources.getDimension(R.dimen.test_dip_dimen))
.isEqualTo(applyDimension(COMPLEX_UNIT_DIP, 20, resources.getDisplayMetrics()));
assertThat(resources.getDimension(R.dimen.test_dp_dimen))
.isEqualTo(applyDimension(COMPLEX_UNIT_DIP, 8, resources.getDisplayMetrics()));
assertThat(resources.getDimension(R.dimen.test_in_dimen))
.isEqualTo(applyDimension(COMPLEX_UNIT_IN, 99, resources.getDisplayMetrics()));
assertThat(resources.getDimension(R.dimen.test_mm_dimen))
.isEqualTo(applyDimension(COMPLEX_UNIT_MM, 42, resources.getDisplayMetrics()));
assertThat(resources.getDimension(R.dimen.test_px_dimen))
.isEqualTo(applyDimension(COMPLEX_UNIT_PX, 15, resources.getDisplayMetrics()));
assertThat(resources.getDimension(R.dimen.test_pt_dimen))
.isEqualTo(applyDimension(COMPLEX_UNIT_PT, 12, resources.getDisplayMetrics()));
assertThat(resources.getDimension(R.dimen.test_sp_dimen))
.isEqualTo(applyDimension(COMPLEX_UNIT_SP, 5, resources.getDisplayMetrics()));
}
@Test
public void getDimensionPixelSize() {
assertThat(resources.getDimensionPixelSize(R.dimen.test_dip_dimen))
.isIn(onePixelOf(convertDimension(COMPLEX_UNIT_DIP, 20)));
assertThat(resources.getDimensionPixelSize(R.dimen.test_dp_dimen))
.isIn(onePixelOf(convertDimension(COMPLEX_UNIT_DIP, 8)));
assertThat(resources.getDimensionPixelSize(R.dimen.test_in_dimen))
.isIn(onePixelOf(convertDimension(COMPLEX_UNIT_IN, 99)));
assertThat(resources.getDimensionPixelSize(R.dimen.test_mm_dimen))
.isIn(onePixelOf(convertDimension(COMPLEX_UNIT_MM, 42)));
assertThat(resources.getDimensionPixelSize(R.dimen.test_px_dimen))
.isIn(onePixelOf(convertDimension(COMPLEX_UNIT_PX, 15)));
assertThat(resources.getDimensionPixelSize(R.dimen.test_pt_dimen))
.isIn(onePixelOf(convertDimension(COMPLEX_UNIT_PT, 12)));
assertThat(resources.getDimensionPixelSize(R.dimen.test_sp_dimen))
.isIn(onePixelOf(convertDimension(COMPLEX_UNIT_SP, 5)));
}
private static Range<Integer> onePixelOf(int i) {
return Range.closed(i - 1, i + 1);
}
@Test
public void getDimensionPixelOffset() {
assertThat(resources.getDimensionPixelOffset(R.dimen.test_dip_dimen))
.isEqualTo(convertDimension(COMPLEX_UNIT_DIP, 20));
assertThat(resources.getDimensionPixelOffset(R.dimen.test_dp_dimen))
.isEqualTo(convertDimension(COMPLEX_UNIT_DIP, 8));
assertThat(resources.getDimensionPixelOffset(R.dimen.test_in_dimen))
.isEqualTo(convertDimension(COMPLEX_UNIT_IN, 99));
assertThat(resources.getDimensionPixelOffset(R.dimen.test_mm_dimen))
.isEqualTo(convertDimension(COMPLEX_UNIT_MM, 42));
assertThat(resources.getDimensionPixelOffset(R.dimen.test_px_dimen))
.isEqualTo(convertDimension(COMPLEX_UNIT_PX, 15));
assertThat(resources.getDimensionPixelOffset(R.dimen.test_pt_dimen))
.isEqualTo(convertDimension(COMPLEX_UNIT_PT, 12));
assertThat(resources.getDimensionPixelOffset(R.dimen.test_sp_dimen))
.isEqualTo(convertDimension(COMPLEX_UNIT_SP, 5));
}
private int convertDimension(int unit, float value) {
return (int) applyDimension(unit, value, resources.getDisplayMetrics());
}
@Test
public void getDimension_withReference() {
assertThat(resources.getBoolean(R.bool.reference_to_true)).isEqualTo(true);
}
@Test
public void getStringArray_shouldThrowExceptionIfNotFound() {
assertThrows(Resources.NotFoundException.class, () -> resources.getStringArray(-1));
}
@Test
public void getIntegerArray_shouldThrowExceptionIfNotFound() {
assertThrows(Resources.NotFoundException.class, () -> resources.getIntArray(-1));
}
@Test
public void getQuantityString() {
assertThat(resources.getQuantityString(R.plurals.beer, 1)).isEqualTo("a beer");
assertThat(resources.getQuantityString(R.plurals.beer, 2)).isEqualTo("some beers");
assertThat(resources.getQuantityString(R.plurals.beer, 3)).isEqualTo("some beers");
}
@Test
public void getQuantityText() {
// Feature not supported in legacy (raw) resource mode.
assumeFalse(isRobolectricLegacyMode());
assertThat(resources.getQuantityText(R.plurals.beer, 1)).isEqualTo("a beer");
assertThat(resources.getQuantityText(R.plurals.beer, 2)).isEqualTo("some beers");
assertThat(resources.getQuantityText(R.plurals.beer, 3)).isEqualTo("some beers");
}
@Test
public void getFraction() {
final int myself = 300;
final int myParent = 600;
assertThat(resources.getFraction(R.fraction.half, myself, myParent)).isEqualTo(150f);
assertThat(resources.getFraction(R.fraction.half_of_parent, myself, myParent)).isEqualTo(300f);
assertThat(resources.getFraction(R.fraction.quarter_as_item, myself, myParent)).isEqualTo(75f);
assertThat(resources.getFraction(R.fraction.quarter_of_parent_as_item, myself, myParent)).isEqualTo(150f);
assertThat(resources.getFraction(R.fraction.fifth_as_reference, myself, myParent)).isWithin(0.01f).of(60f);
assertThat(resources.getFraction(R.fraction.fifth_of_parent_as_reference, myself, myParent)).isWithin(0.01f).of(120f);
}
@Test
public void testConfiguration() {
Configuration configuration = resources.getConfiguration();
assertThat(configuration).isNotNull();
assertThat(configuration.locale).isNotNull();
}
@Test
public void testConfigurationReturnsTheSameInstance() {
assertThat(resources.getConfiguration()).isSameInstanceAs(resources.getConfiguration());
}
@Test
public void testNewTheme() {
assertThat(resources.newTheme()).isNotNull();
}
@Test
public void testGetDrawableNullRClass() {
assertThrows(
Resources.NotFoundException.class,
() -> assertThat(resources.getDrawable(-12345)).isInstanceOf(BitmapDrawable.class));
}
@Test
public void testGetAnimationDrawable() {
assertThat(resources.getDrawable(R.anim.animation_list)).isInstanceOf(AnimationDrawable.class);
}
@Test
public void testGetColorDrawable() {
Drawable drawable = resources.getDrawable(R.color.color_with_alpha);
assertThat(drawable).isInstanceOf(ColorDrawable.class);
assertThat(((ColorDrawable) drawable).getColor()).isEqualTo(0x802C76AD);
}
@Test
public void getColor() {
assertThat(resources.getColor(R.color.color_with_alpha)).isEqualTo(0x802C76AD);
}
@Test
public void getColor_withReference() {
assertThat(resources.getColor(R.color.background)).isEqualTo(0xfff5f5f5);
}
@Test
public void testGetColor_Missing() {
assertThrows(Resources.NotFoundException.class, () -> resources.getColor(11234));
}
@Test
public void testGetColorStateList() {
assertThat(resources.getColorStateList(R.color.color_state_list)).isInstanceOf(ColorStateList.class);
}
@Test
public void testGetBitmapDrawable() {
assertThat(resources.getDrawable(R.drawable.an_image)).isInstanceOf(BitmapDrawable.class);
}
@Test
public void testGetNinePatchDrawable() {
assertThat(resources.getDrawable(R.drawable.nine_patch_drawable)).isInstanceOf(NinePatchDrawable.class);
}
@Test
public void testGetBitmapDrawableForUnknownId() {
assertThrows(
Resources.NotFoundException.class,
() ->
assertThat(resources.getDrawable(Integer.MAX_VALUE))
.isInstanceOf(BitmapDrawable.class));
}
@Test
public void testGetNinePatchDrawableIntrinsicWidth() {
float density = resources.getDisplayMetrics().density;
NinePatchDrawable ninePatchDrawable =
(NinePatchDrawable) resources.getDrawable(R.drawable.nine_patch_drawable);
// Use Math.round to convert calculated float width to int,
// see NinePatchDrawable#scaleFromDensity.
assertThat(ninePatchDrawable.getIntrinsicWidth()).isEqualTo(Math.round(98.0f * density));
}
@Test
public void testGetIdentifier() {
final String resourceType = "string";
final String packageName = context.getPackageName();
final String resourceName = "hello";
final int resId1 = resources.getIdentifier(resourceName, resourceType, packageName);
assertThat(resId1).isEqualTo(R.string.hello);
final String typedResourceName = resourceType + "/" + resourceName;
final int resId2 = resources.getIdentifier(typedResourceName, resourceType, packageName);
assertThat(resId2).isEqualTo(R.string.hello);
final String fqn = packageName + ":" + typedResourceName;
final int resId3 = resources.getIdentifier(fqn, resourceType, packageName);
assertThat(resId3).isEqualTo(R.string.hello);
}
@Test
public void getIdentifier() {
String string = resources.getString(R.string.hello);
assertThat(string).isEqualTo("Hello");
int id = resources.getIdentifier("hello", "string", context.getPackageName());
assertThat(id).isEqualTo(R.string.hello);
String hello = resources.getString(id);
assertThat(hello).isEqualTo("Hello");
}
@Test
public void getIdentifier_nonExistantResource() {
int id = resources.getIdentifier("just_alot_of_crap", "string", context.getPackageName());
assertThat(id).isEqualTo(0);
}
/**
* Public framework symbols are defined here:
* https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/public.xml
* Private framework symbols are defined here:
* https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/symbols.xml
*
* <p>These generate android.R and com.android.internal.R respectively, when Framework Java code
* does not need to reference a framework resource it will not have an R value generated.
* Robolectric is then missing an identifier for this resource so we must generate a placeholder
* ourselves.
*/
@Test
// @Config(sdk = Build.VERSION_CODES.LOLLIPOP) // android:color/secondary_text_material_dark was
// added in API 21
@SdkSuppress(minSdkVersion = LOLLIPOP)
@Config(minSdk = LOLLIPOP)
public void shouldGenerateIdsForResourcesThatAreMissingRValues() {
int identifierMissingFromRFile =
resources.getIdentifier("secondary_text_material_dark", "color", "android");
// We expect Robolectric to generate a placeholder identifier where one was not generated in the
// android R files.
assertThat(identifierMissingFromRFile).isNotEqualTo(0);
// We expect to be able to successfully android:color/secondary_text_material_dark to a
// ColorStateList.
assertThat(resources.getColorStateList(identifierMissingFromRFile)).isNotNull();
}
@Test
public void getSystemShouldReturnSystemResources() {
assertThat(Resources.getSystem()).isInstanceOf(Resources.class);
}
@Test
public void multipleCallsToGetSystemShouldReturnSameInstance() {
assertThat(Resources.getSystem()).isEqualTo(Resources.getSystem());
}
@Test
public void applicationResourcesShouldHaveBothSystemAndLocalValues() {
assertThat(context.getResources().getString(android.R.string.copy)).isEqualTo("Copy");
assertThat(context.getResources().getString(R.string.copy)).isEqualTo("Local Copy");
}
@Test
public void systemResourcesShouldReturnCorrectSystemId() {
assertThat(Resources.getSystem().getIdentifier("copy", "string", "android"))
.isEqualTo(android.R.string.copy);
}
@Test
public void systemResourcesShouldReturnZeroForLocalId() {
assertThat(Resources.getSystem().getIdentifier("copy", "string", context.getPackageName()))
.isEqualTo(0);
}
@Test
public void testGetXml() throws Exception {
XmlResourceParser parser = resources.getXml(R.xml.preferences);
assertThat(parser).isNotNull();
assertThat(findRootTag(parser)).isEqualTo("PreferenceScreen");
parser = resources.getXml(R.layout.custom_layout);
assertThat(parser).isNotNull();
assertThat(findRootTag(parser)).isEqualTo("org.robolectric.android.CustomView");
parser = resources.getXml(R.menu.test);
assertThat(parser).isNotNull();
assertThat(findRootTag(parser)).isEqualTo("menu");
parser = resources.getXml(R.drawable.rainbow);
assertThat(parser).isNotNull();
assertThat(findRootTag(parser)).isEqualTo("layer-list");
parser = resources.getXml(R.anim.test_anim_1);
assertThat(parser).isNotNull();
assertThat(findRootTag(parser)).isEqualTo("set");
parser = resources.getXml(R.color.color_state_list);
assertThat(parser).isNotNull();
assertThat(findRootTag(parser)).isEqualTo("selector");
}
@Test
public void testGetXml_nonexistentResource() {
assertThrows(Resources.NotFoundException.class, () -> resources.getXml(0));
}
@Test
public void testGetXml_nonxmlfile() {
assertThrows(Resources.NotFoundException.class, () -> resources.getXml(R.drawable.an_image));
}
@Test
public void testGetXml_notNPEAfterClose() {
XmlResourceParser parser = resources.getXml(R.xml.preferences);
parser.close();
// the following methods should not NPE if the XmlResourceParser has been closed.
assertThat(parser.getName()).isNull();
assertThat(parser.getNamespace()).isEmpty();
assertThat(parser.getText()).isNull();
}
@Test
public void openRawResource_shouldLoadRawResources() {
InputStream resourceStream = resources.openRawResource(R.raw.raw_resource);
assertThat(resourceStream).isNotNull();
// assertThat(TestUtil.readString(resourceStream)).isEqualTo("raw txt file contents");
}
@Test
public void openRawResource_shouldLoadDrawables() {
InputStream resourceStream = resources.openRawResource(R.drawable.an_image);
Bitmap bitmap = BitmapFactory.decodeStream(resourceStream);
assertThat(bitmap.getHeight()).isEqualTo(53);
assertThat(bitmap.getWidth()).isEqualTo(64);
}
@Test
public void openRawResource_withNonFile_throwsNotFoundException() {
try {
resources.openRawResource(R.string.hello);
fail("should throw");
} catch (Resources.NotFoundException e) {
// cool
}
try {
resources.openRawResource(R.string.hello, new TypedValue());
fail("should throw");
} catch (Resources.NotFoundException e) {
// cool
}
try {
resources.openRawResource(-1234, new TypedValue());
fail("should throw");
} catch (Resources.NotFoundException e) {
// cool
}
}
@Test
@Ignore("todo: incorrect behavior on robolectric vs framework?")
public void openRawResourceFd_returnsNull_todo_FIX() {
assertThat(resources.openRawResourceFd(R.raw.raw_resource)).isNull();
}
@Test
public void openRawResourceFd_withNonFile_throwsNotFoundException() {
try {
resources.openRawResourceFd(R.string.hello);
fail("should throw");
} catch (Resources.NotFoundException e) {
// cool
}
try {
resources.openRawResourceFd(-1234);
fail("should throw");
} catch (Resources.NotFoundException e) {
// cool
}
}
@Test
public void getXml_withNonFile_throwsNotFoundException() {
try {
resources.getXml(R.string.hello);
fail("should throw");
} catch (Resources.NotFoundException e) {
// cool
}
try {
resources.getXml(-1234);
fail("should throw");
} catch (Resources.NotFoundException e) {
// cool
}
}
@Test
public void themeResolveAttribute_shouldSupportNotDereferencingResource() {
TypedValue out = new TypedValue();
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.MyBlackTheme, false);
theme.resolveAttribute(android.R.attr.windowBackground, out, false);
assertThat(out.type).isEqualTo(TYPE_REFERENCE);
assertThat(out.data).isEqualTo(android.R.color.black);
}
@Test
public void obtainAttributes_shouldReturnValuesFromResources() throws Exception {
XmlPullParser parser = resources.getXml(R.xml.xml_attrs);
parser.next();
parser.next();
AttributeSet attributes = Xml.asAttributeSet(parser);
TypedArray typedArray =
resources.obtainAttributes(
attributes, new int[] {android.R.attr.title, android.R.attr.scrollbarFadeDuration});
assertThat(typedArray.getString(0)).isEqualTo("Android Title");
assertThat(typedArray.getInt(1, 0)).isEqualTo(1111);
typedArray.recycle();
}
// @Test
// public void obtainAttributes_shouldUseReferencedIdFromAttributeSet() throws Exception {
// // android:id/mask was introduced in API 21, but it's still possible for apps built against API 21 to refer to it
// // in older runtimes because referenced resource ids are compiled (by aapt) into the binary XML format.
// AttributeSet attributeSet = Robolectric.buildAttributeSet()
// .addAttribute(android.R.attr.id, "@android:id/mask").build();
// TypedArray typedArray = resources.obtainAttributes(attributeSet, new int[]{android.R.attr.id});
// assertThat(typedArray.getResourceId(0, -9)).isEqualTo(android.R.id.mask);
// }
@Test
public void obtainStyledAttributesShouldDereferenceValues() {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.MyBlackTheme, false);
TypedArray arr = theme.obtainStyledAttributes(new int[]{android.R.attr.windowBackground});
TypedValue value = new TypedValue();
arr.getValue(0, value);
arr.recycle();
assertThat(value.type).isAtLeast(TYPE_FIRST_COLOR_INT);
assertThat(value.type).isAtMost(TYPE_LAST_INT);
}
// @Test
// public void obtainStyledAttributes_shouldCheckXmlFirst_fromAttributeSetBuilder() throws Exception {
//
// // This simulates a ResourceProvider built from a 21+ SDK as viewportHeight / viewportWidth were introduced in API 21
// // but the public ID values they are assigned clash with private com.android.internal.R values on older SDKs. This
// // test ensures that even on older SDKs, on calls to obtainStyledAttributes() Robolectric will first check for matching
// // resource ID values in the AttributeSet before checking the theme.
//
// AttributeSet attributes = Robolectric.buildAttributeSet()
// .addAttribute(android.R.attr.viewportWidth, "12.0")
// .addAttribute(android.R.attr.viewportHeight, "24.0")
// .build();
//
// TypedArray typedArray = context.getTheme().obtainStyledAttributes(attributes, new int[] {
// android.R.attr.viewportWidth,
// android.R.attr.viewportHeight
// }, 0, 0);
// assertThat(typedArray.getFloat(0, 0)).isEqualTo(12.0f);
// assertThat(typedArray.getFloat(1, 0)).isEqualTo(24.0f);
// typedArray.recycle();
// }
@Test
public void obtainStyledAttributes_shouldCheckXmlFirst_fromXmlLoadedFromResources() throws Exception {
// This simulates a ResourceProvider built from a 21+ SDK as viewportHeight / viewportWidth were introduced in API 21
// but the public ID values they are assigned clash with private com.android.internal.R values on older SDKs. This
// test ensures that even on older SDKs, on calls to obtainStyledAttributes() Robolectric will first check for matching
// resource ID values in the AttributeSet before checking the theme.
XmlResourceParser xml = context.getResources().getXml(R.drawable.vector);
xml.next();
xml.next();
AttributeSet attributeSet = Xml.asAttributeSet(xml);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attributeSet, new int[] {
android.R.attr.viewportWidth,
android.R.attr.viewportHeight
}, 0, 0);
assertThat(typedArray.getFloat(0, 0)).isEqualTo(12.0f);
assertThat(typedArray.getFloat(1, 0)).isEqualTo(24.0f);
typedArray.recycle();
}
@Test
@SdkSuppress(minSdkVersion = LOLLIPOP)
@Config(minSdk = LOLLIPOP)
public void whenAttrIsDefinedInRuntimeSdk_getResourceName_findsResource() {
assertThat(context.getResources().getResourceName(android.R.attr.viewportHeight))
.isEqualTo("android:attr/viewportHeight");
}
@Test
@SdkSuppress(maxSdkVersion = KITKAT)
@Config(maxSdk = KITKAT_WATCH)
public void whenAttrIsNotDefinedInRuntimeSdk_getResourceName_doesntFindRequestedResourceButInsteadFindsInternalResourceWithSameId() {
// asking for an attr defined after the current SDK doesn't have a defined result; in this case it returns
// numberPickerStyle from com.internal.android.R
assertThat(context.getResources().getResourceName(android.R.attr.viewportHeight))
.isNotEqualTo("android:attr/viewportHeight");
assertThat(context.getResources().getIdentifier("viewportHeight", "attr", "android"))
.isEqualTo(0);
}
@Test
public void subClassInitializedOK() {
SubClassResources subClassResources = new SubClassResources(resources);
assertThat(subClassResources.openRawResource(R.raw.raw_resource)).isNotNull();
}
@Test
public void applyStyleForced() {
final Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.MyBlackTheme, true);
TypedArray arr = theme.obtainStyledAttributes(new int[]{android.R.attr.windowBackground, android.R.attr.textColorHint});
final TypedValue blackBackgroundColor = new TypedValue();
arr.getValue(0, blackBackgroundColor);
assertThat(blackBackgroundColor.resourceId).isEqualTo(android.R.color.black);
arr.recycle();
theme.applyStyle(R.style.MyBlueTheme, true);
arr = theme.obtainStyledAttributes(new int[]{android.R.attr.windowBackground, android.R.attr.textColor, android.R.attr.textColorHint});
final TypedValue blueBackgroundColor = new TypedValue();
arr.getValue(0, blueBackgroundColor);
assertThat(blueBackgroundColor.resourceId).isEqualTo(R.color.blue);
final TypedValue blueTextColor = new TypedValue();
arr.getValue(1, blueTextColor);
assertThat(blueTextColor.resourceId).isEqualTo(R.color.white);
final TypedValue blueTextColorHint = new TypedValue();
arr.getValue(2, blueTextColorHint);
assertThat(blueTextColorHint.resourceId).isEqualTo(android.R.color.darker_gray);
arr.recycle();
}
@Test
public void applyStyleNotForced() {
final Resources.Theme theme = resources.newTheme();
// Apply black theme
theme.applyStyle(R.style.MyBlackTheme, true);
TypedArray arr = theme.obtainStyledAttributes(new int[]{android.R.attr.windowBackground, android.R.attr.textColorHint});
final TypedValue blackBackgroundColor = new TypedValue();
arr.getValue(0, blackBackgroundColor);
assertThat(blackBackgroundColor.resourceId).isEqualTo(android.R.color.black);
final TypedValue blackTextColorHint = new TypedValue();
arr.getValue(1, blackTextColorHint);
assertThat(blackTextColorHint.resourceId).isEqualTo(android.R.color.darker_gray);
arr.recycle();
// Apply blue theme
theme.applyStyle(R.style.MyBlueTheme, false);
arr = theme.obtainStyledAttributes(new int[]{android.R.attr.windowBackground, android.R.attr.textColor, android.R.attr.textColorHint});
final TypedValue blueBackgroundColor = new TypedValue();
arr.getValue(0, blueBackgroundColor);
assertThat(blueBackgroundColor.resourceId).isEqualTo(android.R.color.black);
final TypedValue blueTextColor = new TypedValue();
arr.getValue(1, blueTextColor);
assertThat(blueTextColor.resourceId).isEqualTo(R.color.white);
final TypedValue blueTextColorHint = new TypedValue();
arr.getValue(2, blueTextColorHint);
assertThat(blueTextColorHint.resourceId).isEqualTo(android.R.color.darker_gray);
arr.recycle();
}
@Test
public void getValueShouldClearTypedArrayBetweenCalls() {
TypedValue outValue = new TypedValue();
resources.getValue(R.string.hello, outValue, true);
assertThat(outValue.type).isEqualTo(TYPE_STRING);
assertThat(outValue.string).isEqualTo(resources.getString(R.string.hello));
// outValue.data is an index into the String block which we don't know for raw xml resources.
assertThat(outValue.assetCookie).isNotEqualTo(0);
resources.getValue(R.color.blue, outValue, true);
assertThat(outValue.type).isEqualTo(TYPE_INT_COLOR_RGB8);
assertThat(outValue.data).isEqualTo(0xFF0000FF);
assertThat(outValue.string).isNull();
// outValue.assetCookie is not supported with raw XML
resources.getValue(R.integer.loneliest_number, outValue, true);
assertThat(outValue.type).isEqualTo(TYPE_INT_DEC);
assertThat(outValue.data).isEqualTo(1);
assertThat(outValue.string).isNull();
resources.getValue(R.bool.true_bool_value, outValue, true);
assertThat(outValue.type).isEqualTo(TYPE_INT_BOOLEAN);
assertThat(outValue.data).isNotEqualTo(0); // true == traditionally 0xffffffff, -1 in Java but
// tests should be checking for non-zero
assertThat(outValue.string).isNull();
}
@Test
public void getXml() throws Exception {
XmlResourceParser xmlResourceParser = resources.getXml(R.xml.preferences);
assertThat(xmlResourceParser).isNotNull();
assertThat(xmlResourceParser.next()).isEqualTo(XmlResourceParser.START_DOCUMENT);
assertThat(xmlResourceParser.next()).isEqualTo(XmlResourceParser.START_TAG);
assertThat(xmlResourceParser.getName()).isEqualTo("PreferenceScreen");
}
@Test
public void getXml_shouldParseEmojiCorrectly() throws IOException, XmlPullParserException {
XmlResourceParser xmlResourceParser = resources.getXml(R.xml.has_emoji);
xmlResourceParser.next();
xmlResourceParser.next();
assertThat(xmlResourceParser.getName()).isEqualTo("EmojiRoot");
AttributeSet attributeSet = Xml.asAttributeSet(xmlResourceParser);
assertThat(attributeSet.getAttributeValue(null, "label1")).isEqualTo("no emoji");
String pureEmoji = "\uD83D\uDE00";
assertThat(attributeSet.getAttributeValue(null, "label2")).isEqualTo(pureEmoji);
assertThat(attributeSet.getAttributeValue(null, "label3")).isEqualTo(pureEmoji);
String mixEmojiAndText = "\uD83D\uDE00internal1\uD83D\uDE00internal2\uD83D\uDE00";
assertThat(attributeSet.getAttributeValue(null, "label4")).isEqualTo(mixEmojiAndText);
assertThat(attributeSet.getAttributeValue(null, "label5")).isEqualTo(mixEmojiAndText);
assertThat(attributeSet.getAttributeValue(null, "label6"))
.isEqualTo("don't worry be \uD83D\uDE00");
}
@Test
public void whenMissingXml_throwNotFoundException() {
try {
resources.getXml(0x3038);
fail();
} catch (Resources.NotFoundException e) {
assertThat(e.getMessage()).contains("Resource ID #0x3038");
}
}
@Test
public void stringWithSpaces() {
// this differs from actual Android behavior, which collapses whitespace as "Up to 25 USD"
assertThat(resources.getString(R.string.string_with_spaces, "25", "USD"))
.isEqualTo("Up to 25 USD");
}
@Test
public void internalWhiteSpaceShouldBeCollapsed() {
assertThat(resources.getString(R.string.internal_whitespace_blocks))
.isEqualTo("Whitespace in" + " the middle");
assertThat(resources.getString(R.string.internal_newlines)).isEqualTo("Some Newlines");
}
@Test
public void fontTagWithAttributesShouldBeRead() {
assertThat(resources.getString(R.string.font_tag_with_attribute))
.isEqualTo("This string has a font tag");
}
@Test
public void linkTagWithAttributesShouldBeRead() {
assertThat(resources.getString(R.string.link_tag_with_attribute))
.isEqualTo("This string has a link tag");
}
@Test
public void getResourceTypeName_mipmap() {
assertThat(resources.getResourceTypeName(R.mipmap.mipmap_reference)).isEqualTo("mipmap");
assertThat(resources.getResourceTypeName(R.mipmap.robolectric)).isEqualTo("mipmap");
}
@Test
public void getDrawable_mipmapReferencesResolve() {
Drawable reference = resources.getDrawable(R.mipmap.mipmap_reference);
Drawable original = resources.getDrawable(R.mipmap.robolectric);
assertThat(reference.getMinimumHeight()).isEqualTo(original.getMinimumHeight());
assertThat(reference.getMinimumWidth()).isEqualTo(original.getMinimumWidth());
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@Config(minSdk = Build.VERSION_CODES.O)
public void getDrawable_mipmapReferencesResolveXml() {
Drawable reference = resources.getDrawable(R.mipmap.robolectric_xml);
Drawable original = resources.getDrawable(R.mipmap.mipmap_reference_xml);
assertThat(reference.getMinimumHeight()).isEqualTo(original.getMinimumHeight());
assertThat(reference.getMinimumWidth()).isEqualTo(original.getMinimumWidth());
}
@Test
public void forUntouchedThemes_copyTheme_shouldCopyNothing() {
Resources.Theme theme1 = resources.newTheme();
Resources.Theme theme2 = resources.newTheme();
theme2.setTo(theme1);
}
@Test
public void getResourceIdentifier_shouldReturnValueFromRClass() {
assertThat(
resources.getIdentifier("id_declared_in_item_tag", "id", context.getPackageName()))
.isEqualTo(R.id.id_declared_in_item_tag);
assertThat(
resources.getIdentifier("id/id_declared_in_item_tag", null, context.getPackageName()))
.isEqualTo(R.id.id_declared_in_item_tag);
assertThat(
resources.getIdentifier(context.getPackageName() + ":id_declared_in_item_tag", "id", null))
.isEqualTo(R.id.id_declared_in_item_tag);
assertThat(
resources.getIdentifier(
context.getPackageName() + ":id/id_declared_in_item_tag", "other", "other"))
.isEqualTo(R.id.id_declared_in_item_tag);
}
@Test
public void whenPackageIsUnknown_getResourceIdentifier_shouldReturnZero() {
assertThat(
resources.getIdentifier("whatever", "id", "some.unknown.package"))
.isEqualTo(0);
assertThat(
resources.getIdentifier("id/whatever", null, "some.unknown.package"))
.isEqualTo(0);
assertThat(
resources.getIdentifier("some.unknown.package:whatever", "id", null))
.isEqualTo(0);
assertThat(
resources.getIdentifier("some.unknown.package:id/whatever", "other", "other"))
.isEqualTo(0);
assertThat(
resources.getIdentifier("whatever", "drawable", "some.unknown.package"))
.isEqualTo(0);
assertThat(
resources.getIdentifier("drawable/whatever", null, "some.unknown.package"))
.isEqualTo(0);
assertThat(
resources.getIdentifier("some.unknown.package:whatever", "drawable", null))
.isEqualTo(0);
assertThat(
resources.getIdentifier("some.unknown.package:id/whatever", "other", "other"))
.isEqualTo(0);
}
@Test
@Ignore(
"currently ids are always automatically assigned a value; to fix this we'd need to check "
+ "layouts for +@id/___, which is expensive")
public void whenCalledForIdWithNameNotInRClassOrXml_getResourceIdentifier_shouldReturnZero()
throws Exception {
assertThat(
resources.getIdentifier(
"org.robolectric:id/idThatDoesntExistAnywhere", "other", "other"))
.isEqualTo(0);
}
@Test
public void
whenIdIsAbsentInXmlButPresentInRClass_getResourceIdentifier_shouldReturnIdFromRClass_probablyBecauseItWasDeclaredInALayout()
throws Exception {
assertThat(
resources.getIdentifier("id_declared_in_layout", "id", context.getPackageName()))
.isEqualTo(R.id.id_declared_in_layout);
}
@Test
public void whenResourceIsAbsentInXml_getResourceIdentifier_shouldReturn0() {
assertThat(
resources.getIdentifier("fictitiousDrawable", "drawable", context.getPackageName()))
.isEqualTo(0);
}
@Test
public void whenResourceIsAbsentInXml_getResourceIdentifier_shouldReturnId() {
assertThat(
resources.getIdentifier("an_image", "drawable", context.getPackageName()))
.isEqualTo(R.drawable.an_image);
}
@Test
public void whenResourceIsXml_getResourceIdentifier_shouldReturnId() {
assertThat(
resources.getIdentifier("preferences", "xml", context.getPackageName()))
.isEqualTo(R.xml.preferences);
}
@Test
public void whenResourceIsRaw_getResourceIdentifier_shouldReturnId() {
assertThat(
resources.getIdentifier("raw_resource", "raw", context.getPackageName()))
.isEqualTo(R.raw.raw_resource);
}
@Test
public void getResourceValue_colorARGB8() {
TypedValue outValue = new TypedValue();
resources.getValue(test_ARGB8, outValue, false);
assertThat(outValue.type).isEqualTo(TYPE_INT_COLOR_ARGB8);
assertThat(Color.blue(outValue.data)).isEqualTo(2);
}
@Test
public void getResourceValue_colorRGB8() {
TypedValue outValue = new TypedValue();
resources.getValue(test_RGB8, outValue, false);
assertThat(outValue.type).isEqualTo(TYPE_INT_COLOR_RGB8);
assertThat(Color.blue(outValue.data)).isEqualTo(4);
}
@Test
public void getResourceEntryName_forStyle() {
assertThat(resources.getResourceEntryName(android.R.style.TextAppearance_Small))
.isEqualTo("TextAppearance.Small");
}
@Test
@SdkSuppress(minSdkVersion = O)
@Config(minSdk = O)
public void getFont() {
// Feature not supported in legacy (raw) resource mode.
assumeFalse(isRobolectricLegacyMode());
Typeface typeface = resources.getFont(R.font.vt323_regular);
assertThat(typeface).isNotNull();
}
@Test
@SdkSuppress(minSdkVersion = O)
@Config(minSdk = O)
public void getFontFamily() {
// Feature not supported in legacy (raw) resource mode.
assumeFalse(isRobolectricLegacyMode());
Typeface typeface = resources.getFont(R.font.vt323);
assertThat(typeface).isNotNull();
}
@Test
@SdkSuppress(minSdkVersion = O)
@Config(minSdk = O)
public void getFontFamily_downloadable() {
// Feature not supported in legacy (raw) resource mode.
assumeFalse(isRobolectricLegacyMode());
Typeface typeface = resources.getFont(R.font.downloadable);
assertThat(typeface).isNotNull();
}
@Test
@SdkSuppress(minSdkVersion = Q)
@Config(minSdk = Q)
public void testFontBuilder() throws Exception {
// Used to throw `java.io.IOException: Failed to read font contents`
new Font.Builder(context.getResources(), R.font.vt323_regular).build();
}
@Test
@SdkSuppress(minSdkVersion = Q)
@Config(minSdk = Q)
public void getAttributeSetSourceResId() {
XmlResourceParser xmlResourceParser = resources.getXml(R.xml.preferences);
int sourceResId = Resources.getAttributeSetSourceResId(xmlResourceParser);
assertThat(sourceResId).isEqualTo(R.xml.preferences);
}
private static String findRootTag(XmlResourceParser parser) throws Exception {
int event;
do {
event = parser.next();
} while (event != XmlPullParser.START_TAG);
return parser.getName();
}
private static class SubClassResources extends Resources {
public SubClassResources(Resources res) {
super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
}
}
private static boolean isRobolectricLegacyMode() {
try {
Class<?> runtimeEnvironmentClass = Class.forName("org.robolectric.RuntimeEnvironment");
Method useLegacyResourcesMethod =
runtimeEnvironmentClass.getDeclaredMethod("useLegacyResources");
return (boolean) useLegacyResourcesMethod.invoke(null);
} catch (Exception e) {
return false;
}
}
}