test(home): 장르 크리에이터 페이지 검증을 추가한다

This commit is contained in:
2026-06-04 19:38:08 +09:00
parent c733796aeb
commit bb29fc8010

View File

@@ -2,24 +2,32 @@ package kr.co.vividnext.sodalive.v2.main.home
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.graphics.drawable.ColorDrawable
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.GridLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.widget.NestedScrollView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationFirstAudioContentUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCreatorUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorGroupUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.visibleHomeGenreCreatorGroups
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeAiCharacterAdapter
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeFirstAudioAdapter
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeFollowAllButtonBinder
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeGenreCreatorAdapter
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeLiveAdapter
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeRecentDebutCreatorAdapter
import kr.co.vividnext.sodalive.v2.widget.AudioContentCardView
@@ -35,6 +43,7 @@ import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import java.util.Locale
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28], application = Application::class)
@@ -263,6 +272,219 @@ class HomeMainFragmentLayoutTest {
assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, viewHolder.itemView.layoutParams.height)
}
@Test
fun `genre creator group item matches figma snapping card structure`() {
val group = inflateViewWithParent(R.layout.item_home_genre_creator_group)
val titleGenre = group.findViewById<TextView>(R.id.tv_home_genre_creator_group_title_genre)
val titleSuffix = group.findViewById<TextView>(R.id.tv_home_genre_creator_group_title_suffix)
val creatorGrid = group.findViewById<GridLayout>(R.id.gl_home_genre_creator_profiles)
val followAllButton = group.findViewById<View>(R.id.view_home_genre_group_follow_all)
assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, group.layoutParams.width)
assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, group.layoutParams.height)
assertEquals(R.drawable.bg_home_genre_creator_group, shadowOf(group.background).createdFromResId)
assertEquals(14.dpToPx(), group.paddingStart)
assertEquals(14.dpToPx(), group.paddingTop)
assertEquals(14.dpToPx(), group.paddingEnd)
assertEquals(14.dpToPx(), group.paddingBottom)
assertNotNull(titleGenre)
assertNotNull(titleSuffix)
assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, creatorGrid.layoutParams.width)
assertEquals(4, creatorGrid.columnCount)
assertEquals(2, creatorGrid.rowCount)
assertNotNull(followAllButton)
}
@Test
fun `genre creator adapter sizes group card to parent width minus horizontal padding`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val parent = RecyclerView(context)
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
parent.layout(0, 0, 402.dpToPx(), 600.dpToPx())
val viewHolder = HomeGenreCreatorAdapter(onFollowAllClick = {}).onCreateViewHolder(parent, 0)
assertEquals(374.dpToPx(), viewHolder.itemView.layoutParams.width)
}
@Test
fun `genre creator adapter sizes profile cells to fit four columns in card grid`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val parent = RecyclerView(context)
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
parent.layout(0, 0, 360.dpToPx(), 600.dpToPx())
val adapter = HomeGenreCreatorAdapter(onFollowAllClick = {})
adapter.submitGroups(listOf(genreGroup("genre", genreCreators(1L))))
val viewHolder = adapter.onCreateViewHolder(parent, 0)
adapter.onBindViewHolder(viewHolder, 0)
val creatorGrid = viewHolder.itemView.findViewById<GridLayout>(R.id.gl_home_genre_creator_profiles)
val firstProfile = creatorGrid.getChildAt(0)
val expectedCellSize = ((360 - 28 - 28 - 14 * 3) / 4).dpToPx()
assertEquals(expectedCellSize, firstProfile.layoutParams.width)
}
@Test
fun `genre creator adapter uses recycler width when group width is not resolved`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val parent = RecyclerView(context)
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
parent.layout(0, 0, 360.dpToPx(), 600.dpToPx())
val adapter = HomeGenreCreatorAdapter(onFollowAllClick = {})
adapter.submitGroups(listOf(genreGroup("genre", genreCreators(1L))))
val viewHolder = adapter.onCreateViewHolder(parent, 0)
viewHolder.itemView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
adapter.onBindViewHolder(viewHolder, 0)
val creatorGrid = viewHolder.itemView.findViewById<GridLayout>(R.id.gl_home_genre_creator_profiles)
val firstProfile = creatorGrid.getChildAt(0)
val expectedCellSize = ((360 - 28 - 28 - 14 * 3) / 4).dpToPx()
assertEquals(expectedCellSize, firstProfile.layoutParams.width)
}
@Test
fun `genre creator adapter caps measured group width to recycler content width`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val parent = RecyclerView(context)
parent.setPadding(14.dpToPx(), 0, 14.dpToPx(), 0)
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
parent.layout(0, 0, 402.dpToPx(), 600.dpToPx())
val adapter = HomeGenreCreatorAdapter(onFollowAllClick = {})
adapter.submitGroups(listOf(genreGroup("genre", genreCreators(1L))))
val viewHolder = adapter.onCreateViewHolder(parent, 0)
viewHolder.itemView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
viewHolder.itemView.layout(0, 0, 402.dpToPx(), 300.dpToPx())
adapter.onBindViewHolder(viewHolder, 0)
val creatorGrid = viewHolder.itemView.findViewById<GridLayout>(R.id.gl_home_genre_creator_profiles)
val firstProfile = creatorGrid.getChildAt(0)
val expectedCellSize = ((402 - 28 - 28 - 14 * 3) / 4).dpToPx()
assertEquals(expectedCellSize, firstProfile.layoutParams.width)
}
@Test
fun `genre creator adapter uses measured grid width for profile cells`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val parent = RecyclerView(context)
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
parent.layout(0, 0, 402.dpToPx(), 600.dpToPx())
val adapter = HomeGenreCreatorAdapter(onFollowAllClick = {})
adapter.submitGroups(listOf(genreGroup("genre", genreCreators(1L))))
val viewHolder = adapter.onCreateViewHolder(parent, 0)
val creatorGrid = viewHolder.itemView.findViewById<GridLayout>(R.id.gl_home_genre_creator_profiles)
viewHolder.itemView.layout(0, 0, 374.dpToPx(), 300.dpToPx())
creatorGrid.layout(0, 0, 300.dpToPx(), 200.dpToPx())
adapter.onBindViewHolder(viewHolder, 0)
val firstProfile = creatorGrid.getChildAt(0)
val expectedCellSize = ((300 - 14 * 3) / 4).dpToPx()
assertEquals(expectedCellSize, firstProfile.layoutParams.width)
}
@Test
fun `genre creator suffix preserves leading spaces only for spaced languages`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val englishContext = context.createConfigurationContext(
Configuration(context.resources.configuration).apply { setLocale(Locale.ENGLISH) }
)
val japaneseContext = context.createConfigurationContext(
Configuration(context.resources.configuration).apply { setLocale(Locale.JAPANESE) }
)
assertEquals(true, context.getString(R.string.home_recommendation_section_genre_creator_suffix).startsWith(" "))
assertEquals(true, englishContext.getString(R.string.home_recommendation_section_genre_creator_suffix).startsWith(" "))
assertEquals(false, japaneseContext.getString(R.string.home_recommendation_section_genre_creator_suffix).startsWith(" "))
}
@Test
fun `genre creator profile item uses parent cell width and square image ratio`() {
val profile = inflateViewWithParent(R.layout.item_home_genre_creator_profile)
val profileImage = profile.findViewById<ImageView>(R.id.iv_home_genre_creator_profile)
val nickname = profile.findViewById<TextView>(R.id.tv_home_genre_creator_profile_nickname)
val imageLayoutParams = profileImage.layoutParams as ConstraintLayout.LayoutParams
assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, profile.layoutParams.width)
assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, profile.layoutParams.height)
assertEquals(0, imageLayoutParams.width)
assertEquals(0, imageLayoutParams.height)
assertEquals("1:1", imageLayoutParams.dimensionRatio)
assertEquals(6.dpToPx(), (nickname.layoutParams as ViewGroup.MarginLayoutParams).topMargin)
assertEquals(14f, nickname.textSize / nickname.resources.displayMetrics.scaledDensity)
}
@Test
fun `home genre creator section uses pager snapping list`() {
val root = inflateView(R.layout.fragment_v2_main_home)
val genreList = root.findViewById<RecyclerView>(R.id.rv_home_genre_creators)
assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, genreList.layoutParams.height)
assertEquals(14.dpToPx(), genreList.paddingStart)
assertEquals(14.dpToPx(), genreList.paddingEnd)
assertNotNull(genreList.onFlingListener)
}
@Test
fun `genre creator adapter renders max five non empty genre groups`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val parent = RecyclerView(context)
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
val adapter = HomeGenreCreatorAdapter(onFollowAllClick = {})
adapter.submitGroups(
listOf(
genreGroup("empty", emptyList()),
genreGroup("genre1", genreCreators(1L)),
genreGroup("genre2", genreCreators(11L)),
genreGroup("genre3", genreCreators(21L)),
genreGroup("genre4", genreCreators(31L)),
genreGroup("genre5", genreCreators(41L)),
genreGroup("genre6", genreCreators(51L))
)
)
val viewHolder = adapter.onCreateViewHolder(parent, 0)
adapter.onBindViewHolder(viewHolder, 0)
assertEquals(5, adapter.itemCount)
assertEquals("genre1", viewHolder.itemView.findViewById<TextView>(R.id.tv_home_genre_creator_group_title_genre).text)
assertEquals(8, viewHolder.itemView.findViewById<GridLayout>(R.id.gl_home_genre_creator_profiles).childCount)
}
@Test
fun `genre creator adapter follow all uses ids from its page group`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val parent = RecyclerView(context)
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
val clickedCreatorIds = mutableListOf<Long>()
val adapter = HomeGenreCreatorAdapter(onFollowAllClick = { clickedCreatorIds.addAll(it) })
adapter.submitGroups(listOf(genreGroup("genre", genreCreators(10L))))
val viewHolder = adapter.onCreateViewHolder(parent, 0)
adapter.onBindViewHolder(viewHolder, 0)
viewHolder.itemView.findViewById<View>(R.id.view_home_genre_group_follow_all).performClick()
assertEquals((10L..17L).toList(), clickedCreatorIds)
}
@Test
fun `genre creator visible groups hide all empty groups before fragment visibility`() {
val section = HomeRecommendationGenreCreatorSection(
listOf(
genreGroup("empty1", emptyList()),
genreGroup("empty2", emptyList())
)
)
assertEquals(emptyList<HomeRecommendationGenreCreatorGroupUiModel>(), section.visibleHomeGenreCreatorGroups())
}
@Test
fun `first audio adapter clips thumbnail container`() {
val context = ApplicationProvider.getApplicationContext<Context>()
@@ -414,7 +636,7 @@ class HomeMainFragmentLayoutTest {
}
@Test
fun `home layout uses section title components and custom genre title row`() {
fun `home layout uses section title components and genre title inside page card`() {
val root = inflateView(R.layout.fragment_v2_main_home)
val sectionTitleIds = listOf(
R.id.view_home_recent_activity_title,
@@ -437,9 +659,10 @@ class HomeMainFragmentLayoutTest {
assertNotNull(chevron)
}
assertNotNull(root.findViewById<LinearLayout>(R.id.ll_home_genre_creator_title))
assertNotNull(root.findViewById<TextView>(R.id.tv_home_genre_creator_title_genre))
assertNotNull(root.findViewById<TextView>(R.id.tv_home_genre_creator_title_suffix))
val genreGroup = inflateView(R.layout.item_home_genre_creator_group)
assertNotNull(genreGroup.findViewById<TextView>(R.id.tv_home_genre_creator_group_title_genre))
assertNotNull(genreGroup.findViewById<TextView>(R.id.tv_home_genre_creator_group_title_suffix))
}
@Test
@@ -512,4 +735,21 @@ class HomeMainFragmentLayoutTest {
tags = setOf(AudioContentTag.First, AudioContentTag.Point, AudioContentTag.Free)
)
}
private fun genreGroup(
genre: String,
creators: List<HomeRecommendationCreatorUiModel>
): HomeRecommendationGenreCreatorGroupUiModel {
return HomeRecommendationGenreCreatorGroupUiModel(genre, creators)
}
private fun genreCreators(startId: Long): List<HomeRecommendationCreatorUiModel> {
return (startId until startId + 8L).map { creatorId ->
HomeRecommendationCreatorUiModel(
creatorId = creatorId,
nickname = "크리에이터$creatorId",
profileImage = null
)
}
}
}