feat(banner): 배너 어댑터와 뷰를 추가한다
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
package kr.co.vividnext.sodalive.v2.widget.banner
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.View.MeasureSpec
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], application = Application::class)
|
||||
class BannerViewTest {
|
||||
|
||||
@Test
|
||||
fun `adapter는 빈 목록과 단일 목록에서 실제 아이템 개수를 사용한다`() {
|
||||
val adapter = BannerAdapter()
|
||||
|
||||
adapter.submitItems(emptyList())
|
||||
assertEquals(0, adapter.itemCount)
|
||||
|
||||
adapter.submitItems(listOf(sampleItem("1")))
|
||||
assertEquals(1, adapter.itemCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `adapter는 가상 위치를 실제 아이템으로 변환하고 이미지 바인딩과 클릭 콜백을 전달한다`() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val parent = FrameLayout(context)
|
||||
val items = listOf(sampleItem("1"), sampleItem("2"))
|
||||
var boundImageView: ImageView? = null
|
||||
var boundItem: BannerItem? = null
|
||||
var clickedItem: BannerItem? = null
|
||||
val adapter = BannerAdapter(
|
||||
onClickItem = { clickedItem = it },
|
||||
onBindImage = { imageView, item ->
|
||||
boundImageView = imageView
|
||||
boundItem = item
|
||||
}
|
||||
)
|
||||
|
||||
adapter.submitItems(items)
|
||||
val holder = adapter.onCreateViewHolder(parent, 0)
|
||||
adapter.onBindViewHolder(holder, 3)
|
||||
holder.itemView.performClick()
|
||||
|
||||
assertTrue(adapter.itemCount > items.size)
|
||||
assertSame(holder.itemView.findViewById<ImageView>(R.id.iv_banner), boundImageView)
|
||||
assertEquals(items[1], boundItem)
|
||||
assertEquals(items[1], clickedItem)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `배너 view는 빈 목록이면 숨기고 단일 목록이면 counter를 숨긴다`() {
|
||||
val view = inflateBannerView()
|
||||
|
||||
view.setItems(emptyList())
|
||||
assertEquals(View.GONE, view.visibility)
|
||||
|
||||
view.setItems(listOf(sampleItem("1")))
|
||||
assertEquals(View.VISIBLE, view.visibility)
|
||||
assertEquals(View.GONE, view.findViewById<View>(R.id.layout_banner_counter).visibility)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `배너 view는 코드로 생성해도 내부 layout을 포함한다`() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val view = BannerView(context)
|
||||
|
||||
view.setItems(listOf(sampleItem("1")))
|
||||
|
||||
assertNotNull(view.findViewById<RecyclerView>(R.id.rv_banner))
|
||||
assertEquals(View.VISIBLE, view.visibility)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `배너 view는 carousel 목록이면 형식화된 counter를 표시한다`() {
|
||||
val view = inflateBannerView()
|
||||
|
||||
view.setItems(listOf(sampleItem("1"), sampleItem("2")))
|
||||
|
||||
assertEquals(View.VISIBLE, view.visibility)
|
||||
assertEquals(View.VISIBLE, view.findViewById<View>(R.id.layout_banner_counter).visibility)
|
||||
assertEquals("01", view.findViewById<TextView>(R.id.tv_banner_current_index).text.toString())
|
||||
assertEquals("/", view.findViewById<TextView>(R.id.tv_banner_counter_separator).text.toString())
|
||||
assertEquals("02", view.findViewById<TextView>(R.id.tv_banner_total_count).text.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `배너 view는 정사각형 item 크기와 좌우 padding 및 간격을 적용한다`() {
|
||||
val view = inflateBannerView()
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.rv_banner)
|
||||
|
||||
view.setItems(listOf(sampleItem("1"), sampleItem("2")))
|
||||
view.measure(exactly(402.dpToPx()), exactly(402.dpToPx()))
|
||||
view.layout(0, 0, 402.dpToPx(), 402.dpToPx())
|
||||
val holder = recyclerView.adapter!!.onCreateViewHolder(recyclerView, 0)
|
||||
recyclerView.adapter!!.onBindViewHolder(holder, 0)
|
||||
val itemOffset = Rect()
|
||||
recyclerView.getItemDecorationAt(0).getItemOffsets(itemOffset, holder.itemView, recyclerView, RecyclerView.State())
|
||||
|
||||
assertEquals(20.dpToPx(), recyclerView.paddingLeft)
|
||||
assertEquals(20.dpToPx(), recyclerView.paddingRight)
|
||||
assertEquals(8.dpToPx(), itemOffset.right)
|
||||
assertEquals(362.dpToPx(), holder.itemView.layoutParams.width)
|
||||
assertEquals(362.dpToPx(), holder.itemView.layoutParams.height)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `배너 view는 단일 item이면 item 간격을 적용하지 않는다`() {
|
||||
val view = inflateBannerView()
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.rv_banner)
|
||||
|
||||
view.setItems(listOf(sampleItem("1")))
|
||||
view.measure(exactly(402.dpToPx()), exactly(402.dpToPx()))
|
||||
view.layout(0, 0, 402.dpToPx(), 402.dpToPx())
|
||||
val holder = recyclerView.adapter!!.onCreateViewHolder(recyclerView, 0)
|
||||
recyclerView.adapter!!.onBindViewHolder(holder, 0)
|
||||
val itemOffset = Rect()
|
||||
recyclerView.getItemDecorationAt(0).getItemOffsets(itemOffset, holder.itemView, recyclerView, RecyclerView.State())
|
||||
|
||||
assertEquals(0, itemOffset.right)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `배너 item image는 radius clipping 대상으로 설정된다`() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val adapter = BannerAdapter()
|
||||
adapter.submitItems(listOf(sampleItem("1")))
|
||||
|
||||
val holder = adapter.onCreateViewHolder(FrameLayout(context), 0)
|
||||
adapter.onBindViewHolder(holder, 0)
|
||||
val imageView = holder.itemView.findViewById<ImageView>(R.id.iv_banner)
|
||||
|
||||
assertTrue(imageView.clipToOutline)
|
||||
assertNotNull(imageView.outlineProvider)
|
||||
}
|
||||
|
||||
private fun inflateBannerView(): BannerView {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
return BannerView(context)
|
||||
}
|
||||
|
||||
private fun sampleItem(id: String) = BannerItem(
|
||||
bannerId = id,
|
||||
imageUrl = "https://example.com/banner-$id.png"
|
||||
)
|
||||
|
||||
private fun exactly(size: Int): Int = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
|
||||
|
||||
private fun Int.dpToPx(): Int {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
return (this * context.resources.displayMetrics.density).toInt()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user