diff --git a/app/build.gradle b/app/build.gradle index 6293e3ee..016a0801 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,6 +69,12 @@ android { abortOnError false } + testOptions { + unitTests { + includeAndroidResources = true + } + } + packagingOptions { exclude 'META-INF/library-core_release.kotlin_module' exclude 'META-INF/library_release.kotlin_module' @@ -139,4 +145,6 @@ dependencies { implementation project(':weather-icons-typeface-library') testImplementation 'junit:junit:4.13.2' + testImplementation 'org.robolectric:robolectric:4.16' + testImplementation 'androidx.test:core:1.7.0' } diff --git a/app/src/main/res/menu/menu_playground.xml b/app/src/main/res/menu/menu_playground.xml index 7ad01d40..8c0b7af5 100644 --- a/app/src/main/res/menu/menu_playground.xml +++ b/app/src/main/res/menu/menu_playground.xml @@ -31,4 +31,10 @@ app:ico_color="@android:color/holo_blue_bright" app:ico_icon="gmd_star" app:ico_size="24dp" /> + + + diff --git a/app/src/test/java/com/mikepenz/iconics/sample/IconicsMenuInflaterUtilTest.kt b/app/src/test/java/com/mikepenz/iconics/sample/IconicsMenuInflaterUtilTest.kt new file mode 100644 index 00000000..a8abf767 --- /dev/null +++ b/app/src/test/java/com/mikepenz/iconics/sample/IconicsMenuInflaterUtilTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026 Mike Penz + * + * 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.mikepenz.iconics.sample + +import android.app.Activity +import android.view.Menu +import androidx.appcompat.view.menu.MenuBuilder +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.IconicsMenuInflaterUtil +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner + +/** + * `menu_playground` covers the three relevant cases: + * - `menu_item_1` has a plain `android:icon` and no `ico_*` attributes + * - `menu_item_2` is defined via `ico_*` attributes + * - `menu_item_3` has no icon at all + */ +@RunWith(RobolectricTestRunner::class) +class IconicsMenuInflaterUtilTest { + + private lateinit var activity: Activity + private lateinit var menu: Menu + + @Before fun setUp() { + activity = Robolectric.buildActivity(PlaygroundActivity::class.java).get() + menu = MenuBuilder(activity) + } + + @Test fun `iconics item gets an IconicsDrawable`() { + IconicsMenuInflaterUtil.inflate(activity.menuInflater, activity, R.menu.menu_playground, menu) + + assertTrue(menu.findItem(R.id.menu_item_2).icon is IconicsDrawable) + } + + @Test fun `non-iconics item keeps its android icon`() { + activity.menuInflater.inflate(R.menu.menu_playground, menu) + val original = menu.findItem(R.id.menu_item_1).icon + assertNotNull("menu_item_1 is expected to declare an android:icon", original) + + IconicsMenuInflaterUtil.parseXmlAndSetIconicsDrawables(activity, R.menu.menu_playground, menu) + + assertSame( + "an item without ico_* attributes must keep the drawable of its android:icon", + original, + menu.findItem(R.id.menu_item_1).icon + ) + } + + @Test fun `item without any icon stays without one`() { + IconicsMenuInflaterUtil.inflate(activity.menuInflater, activity, R.menu.menu_playground, menu) + + assertNull(menu.findItem(R.id.menu_item_3).icon) + } +} diff --git a/iconics-core/src/main/java/com/mikepenz/iconics/context/IconicsAttrsApplier.kt b/iconics-core/src/main/java/com/mikepenz/iconics/context/IconicsAttrsApplier.kt index b337a9fd..b51316ae 100644 --- a/iconics-core/src/main/java/com/mikepenz/iconics/context/IconicsAttrsApplier.kt +++ b/iconics-core/src/main/java/com/mikepenz/iconics/context/IconicsAttrsApplier.kt @@ -45,6 +45,11 @@ object IconicsAttrsApplier { @JvmStatic fun getIconicsDrawable(res: Resources, theme: Theme?, attrs: AttributeSet?): IconicsDrawable? { return theme?.obtainStyledAttributes(attrs, R.styleable.Iconics, 0, 0)?.use { + // no `ico_*` attribute at all -> there is nothing to build an icon from. Returning an + // empty drawable here would overwrite whatever icon the target already has, e.g. the + // `android:icon` of a menu item or the `android:src` of an `ImageView`. + if (it.indexCount == 0) return@use null + IconicsAttrsExtractor( res = res, theme = theme, diff --git a/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt b/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt index b2d94762..651c6711 100644 --- a/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt +++ b/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt @@ -168,18 +168,10 @@ object IconicsMenuInflaterUtil { menu: Menu ) { val attrsMap = mutableMapOf() - var hasIconicsAttrs = false repeat(attrs.attributeCount) { - val name = attrs.getAttributeName(it) - attrsMap[name] = attrs.getAttributeValue(it) - if (name.startsWith("ico_")) { - hasIconicsAttrs = true - } + attrsMap[attrs.getAttributeName(it)] = attrs.getAttributeValue(it) } - // Don't unset non-iconics menu item icon - if (!hasIconicsAttrs) return - attrsMap["id"] ?.replace("@", "") ?.removePrefix("+id/")