Skip to content

Commit

Permalink
Merge pull request #122 from btwarog/chip-assertions
Browse files Browse the repository at this point in the history
Chip assertions
  • Loading branch information
Vacxe authored Apr 9, 2024
2 parents 99cd7c1 + 494431c commit 93df790
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.github.kakaocup.kakao.chip

import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.View
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.test.espresso.assertion.ViewAssertions
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import io.github.kakaocup.kakao.common.assertions.BaseAssertions
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher

interface ChipAssertions : BaseAssertions {

/**
* Check if Chip has correct icon for [ChipIconType.CHECKED] icon type.
*
* @param resId Drawable resource to be matched (default is -1)
* @param drawable Drawable instance to be matched (default is null)
* @param tintColorId Tint color resource id (default is null)
* @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
*/
fun hasCheckedIcon(
@DrawableRes resId: Int = -1,
drawable: Drawable? = null,
@ColorRes tintColorId: Int? = null,
toBitmap: ((drawable: Drawable) -> Bitmap)? = null,
) = view.check(ViewAssertions.matches(ChipDrawableMatcher(resId, drawable, ChipIconType.CHECKED, tintColorId, toBitmap)))


/**
* Check if Chip has correct icon for [ChipIconType.CHIP] icon type.
*
* @param resId Drawable resource to be matched (default is -1)
* @param drawable Drawable instance to be matched (default is null)
* @param tintColorId Tint color resource id (default is null)
* @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
*/
fun hasChipIcon(
@DrawableRes resId: Int = -1,
drawable: Drawable? = null,
@ColorRes tintColorId: Int? = null,
toBitmap: ((drawable: Drawable) -> Bitmap)? = null,
) = view.check(ViewAssertions.matches(ChipDrawableMatcher(resId, drawable, ChipIconType.CHIP, tintColorId, toBitmap)))

/**
* Check if Chip has correct icon for [ChipIconType.CLOSE] icon type.
*
* @param resId Drawable resource to be matched (default is -1)
* @param drawable Drawable instance to be matched (default is null)
* @param tintColorId Tint color resource id (default is null)
* @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
*/
fun hasCloseIcon(
@DrawableRes resId: Int = -1,
drawable: Drawable? = null,
@ColorRes tintColorId: Int? = null,
toBitmap: ((drawable: Drawable) -> Bitmap)? = null,
) = view.check(ViewAssertions.matches(ChipDrawableMatcher(resId, drawable, ChipIconType.CLOSE, tintColorId, toBitmap)))

/**
* Verify all icons are hidden
*/
fun hasNoIconVisible() = view.check(ViewAssertions.matches(object : TypeSafeMatcher<View>(View::class.java) {
override fun describeTo(desc: Description) {
desc.appendText("without any icons visible")
}

override fun matchesSafely(view: View): Boolean {
val viewAsChip = view as? Chip ?: return false
return (viewAsChip.chipDrawable as? ChipDrawable)?.verifyNoIconVisible() ?: run {
println("Drawable should be ChipDrawable unless implementation has changed")
false
}
}
}))

fun ChipDrawable.verifyNoIconVisible(): Boolean {
return !isCheckedIconVisible && !isChipIconVisible && !isCloseIconVisible
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.github.kakaocup.kakao.chip

import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.os.Build
import android.view.View
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.material.chip.Chip
import io.github.kakaocup.kakao.common.extentions.toBitmap
import io.github.kakaocup.kakao.common.utilities.getResourceColor
import io.github.kakaocup.kakao.common.utilities.getResourceDrawable
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher

/**
* Matches given drawable with current one for the [chipIconType] drawable type.
*
* @param resId Drawable resource to be matched (default is -1)
* @param drawable Drawable instance to be matched (default is null)
* @param chipIconType drawable type to verify against
* @param tintColorId Tint color resource id (default is null)
* @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
*/
class ChipDrawableMatcher(
@DrawableRes private val resId: Int = -1,
private val drawable: Drawable? = null,
private val chipIconType: ChipIconType,
@ColorRes private val tintColorId: Int? = null,
private val toBitmap: ((drawable: Drawable) -> Bitmap)? = null,
) : TypeSafeMatcher<View>(View::class.java) {

override fun describeTo(desc: Description) {
desc.appendText("with drawable id $resId or provided instance for the $chipIconType icon")
}

override fun matchesSafely(view: View?): Boolean {
val expectedDrawable: Drawable? = when {
drawable != null -> drawable
resId >= 0 -> getResourceDrawable(resId)?.mutate()
else -> return (view as? Chip)?.chipDrawable == null
}

// Apply backward compatibility wrap and tints if necessary
val finalDrawable = expectedDrawable.processDrawableForDueToSdk() ?: return false

// Compare actual vs expected drawables
return (view as? Chip)?.getChipActualDrawable(chipIconType)?.mutate()?.let { actualDrawable ->
val actualBitmap = toBitmap?.invoke(actualDrawable) ?: actualDrawable.toBitmap()
val expectedBitmap = toBitmap?.invoke(finalDrawable) ?: finalDrawable.toBitmap()
actualBitmap.sameAs(expectedBitmap)
} ?: false
}

private fun Drawable?.processDrawableForDueToSdk() = when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && this != null -> DrawableCompat.wrap(
this
)
.mutate()

Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && tintColorId != null -> this?.apply {
val tintColor = getResourceColor(tintColorId)
setTintList(ColorStateList.valueOf(tintColor))
setTintMode(PorterDuff.Mode.SRC_IN)
}

else -> this
}

private fun Chip.getChipActualDrawable(chipIconType: ChipIconType): Drawable? = when (chipIconType) {
ChipIconType.CHECKED -> checkedIcon?.takeIf { isCheckedIconVisible }
ChipIconType.CHIP -> chipIcon?.takeIf { isChipIconVisible }
ChipIconType.CLOSE -> closeIcon?.takeIf { isCloseIconVisible }
}
}

enum class ChipIconType {
CHECKED,
CHIP,
CLOSE,
}
16 changes: 16 additions & 0 deletions kakao/src/main/kotlin/io/github/kakaocup/kakao/chip/KChip.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.kakaocup.kakao.chip

import android.view.View
import androidx.test.espresso.DataInteraction
import io.github.kakaocup.kakao.check.CheckableActions
import io.github.kakaocup.kakao.check.CheckableAssertions
import io.github.kakaocup.kakao.common.builders.ViewBuilder
import io.github.kakaocup.kakao.common.views.KBaseView
import io.github.kakaocup.kakao.text.TextViewAssertions
import org.hamcrest.Matcher

class KChip : KBaseView<KChip>, CheckableActions, CheckableAssertions, TextViewAssertions, ChipAssertions {
constructor(function: ViewBuilder.() -> Unit) : super(function)
constructor(parent: Matcher<View>, function: ViewBuilder.() -> Unit) : super(parent, function)
constructor(parent: DataInteraction, function: ViewBuilder.() -> Unit) : super(parent, function)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.github.kakaocup.sample

import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import io.github.kakaocup.kakao.screen.Screen.Companion.onScreen
import io.github.kakaocup.sample.screen.ChipsScreen
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4ClassRunner::class)
class ChipsTest {
@Rule
@JvmField
val rule = ActivityScenarioRule(ChipsActivity::class.java)

@Test
fun testCorrectChipsDisplayed() {
onScreen<ChipsScreen> {
chip1 {
isChecked()
hasText("Chip1")
hasCheckedIcon(R.drawable.ic_sentiment_very_satisfied_black_24dp)
}
chip2 {
isNotChecked()
hasText("Chip2")
hasChipIcon(R.drawable.ic_auto_fix_high_24dp)
}
chip3 {
isNotChecked()
hasText("Chip3")
hasCloseIcon(R.drawable.ic_android_black_24dp, tintColorId = android.R.color.black)
}
chip4 {
isNotChecked()
hasText("Chip4")
hasNoIconVisible()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.kakaocup.sample.screen

import io.github.kakaocup.kakao.chip.KChip
import io.github.kakaocup.kakao.screen.Screen
import io.github.kakaocup.sample.R

open class ChipsScreen : Screen<ChipsScreen>() {
val chip1: KChip = KChip { withId(R.id.chip1) }
val chip2: KChip = KChip { withId(R.id.chip2) }
val chip3: KChip = KChip { withId(R.id.chip3) }
val chip4: KChip = KChip { withId(R.id.chip4) }
}
4 changes: 4 additions & 0 deletions sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
android:name="io.github.kakaocup.sample.AnimatedButtonClickActivity"
android:label="Animated Button Clicks Activity"
android:theme="@style/MaterialAppTheme" />
<activity
android:name="io.github.kakaocup.sample.ChipsActivity"
android:label="Chips Activity"
android:theme="@style/MaterialAppTheme" />
</application>

</manifest>
11 changes: 11 additions & 0 deletions sample/src/main/kotlin/io/github/kakaocup/sample/ChipsActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.github.kakaocup.sample

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class ChipsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chips)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class TestActivity : AppCompatActivity() {
addRoute(R.id.text_input_layout, TextInputLayoutActivity::class.java)
addRoute(R.id.text_activity, TextActivity::class.java)
addRoute(R.id.switchers_button, SwitchersActivity::class.java)
addRoute(R.id.chips, ChipsActivity::class.java)

findViewById<Button>(R.id.snackbar_button).setOnClickListener {
val snackbar = Snackbar.make(
Expand Down
5 changes: 5 additions & 0 deletions sample/src/main/res/drawable/ic_auto_fix_high_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="#FFF" android:pathData="M7.5,5.6L10,7 8.6,4.5 10,2 7.5,3.4 5,2l1.4,2.5L5,7zM19.5,15.4L17,14l1.4,2.5L17,19l2.5,-1.4L22,19l-1.4,-2.5L22,14zM22,2l-2.5,1.4L17,2l1.4,2.5L17,7l2.5,-1.4L22,7l-1.4,-2.5zM14.37,7.29c-0.39,-0.39 -1.02,-0.39 -1.41,0L1.29,18.96c-0.39,0.39 -0.39,1.02 0,1.41l2.34,2.34c0.39,0.39 1.02,0.39 1.41,0L16.7,11.05c0.39,-0.39 0.39,-1.02 0,-1.41l-2.33,-2.35zM13.34,12.78l-2.12,-2.12 2.44,-2.44 2.12,2.12 -2.44,2.44z"/>

</vector>
58 changes: 58 additions & 0 deletions sample/src/main/res/layout/activity_chips.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:chipSpacing="8dp">

<com.google.android.material.chip.Chip
android:id="@+id/chip1"
style="@style/Widget.MaterialComponents.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
android:checked="true"
app:checkedIcon="@drawable/ic_sentiment_very_satisfied_black_24dp"
android:text="Chip1" />

<com.google.android.material.chip.Chip
android:id="@+id/chip2"
style="@style/Base.Widget.MaterialComponents.Chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
app:chipIcon="@drawable/ic_auto_fix_high_24dp"
app:chipIconVisible="true"
app:closeIconVisible="false"
android:text="Chip2" />

<com.google.android.material.chip.Chip
android:id="@+id/chip3"
style="@style/Base.Widget.MaterialComponents.Chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
app:closeIcon="@drawable/ic_android_black_24dp"
app:closeIconTint="@android:color/black"
android:text="Chip3" />

<com.google.android.material.chip.Chip
android:id="@+id/chip4"
style="@style/Base.Widget.MaterialComponents.Chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
app:closeIconVisible="false"
app:checkedIconVisible="false"
app:chipIconVisible="false"
android:text="Chip4" />

</com.google.android.material.chip.ChipGroup>
</LinearLayout>
6 changes: 6 additions & 0 deletions sample/src/main/res/layout/activity_test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@
android:layout_height="wrap_content"
android:text="SWITCHERS"/>

<Button
android:id="@+id/chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CHIPS"/>

</GridLayout>

<LinearLayout
Expand Down

0 comments on commit 93df790

Please sign in to comment.