test(home): 장르 크리에이터 페이지 검증을 추가한다
This commit is contained in:
@@ -2,24 +2,32 @@ package kr.co.vividnext.sodalive.v2.main.home
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.GridLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.widget.NestedScrollView
|
import androidx.core.widget.NestedScrollView
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import kr.co.vividnext.sodalive.R
|
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.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.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.HomeAiCharacterAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeFirstAudioAdapter
|
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.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.HomeLiveAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeRecentDebutCreatorAdapter
|
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeRecentDebutCreatorAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.widget.AudioContentCardView
|
import kr.co.vividnext.sodalive.v2.widget.AudioContentCardView
|
||||||
@@ -35,6 +43,7 @@ import org.junit.runner.RunWith
|
|||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
import org.robolectric.Shadows.shadowOf
|
import org.robolectric.Shadows.shadowOf
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
@Config(sdk = [28], application = Application::class)
|
@Config(sdk = [28], application = Application::class)
|
||||||
@@ -263,6 +272,219 @@ class HomeMainFragmentLayoutTest {
|
|||||||
assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, viewHolder.itemView.layoutParams.height)
|
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
|
@Test
|
||||||
fun `first audio adapter clips thumbnail container`() {
|
fun `first audio adapter clips thumbnail container`() {
|
||||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
@@ -414,7 +636,7 @@ class HomeMainFragmentLayoutTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 root = inflateView(R.layout.fragment_v2_main_home)
|
||||||
val sectionTitleIds = listOf(
|
val sectionTitleIds = listOf(
|
||||||
R.id.view_home_recent_activity_title,
|
R.id.view_home_recent_activity_title,
|
||||||
@@ -437,9 +659,10 @@ class HomeMainFragmentLayoutTest {
|
|||||||
|
|
||||||
assertNotNull(chevron)
|
assertNotNull(chevron)
|
||||||
}
|
}
|
||||||
assertNotNull(root.findViewById<LinearLayout>(R.id.ll_home_genre_creator_title))
|
val genreGroup = inflateView(R.layout.item_home_genre_creator_group)
|
||||||
assertNotNull(root.findViewById<TextView>(R.id.tv_home_genre_creator_title_genre))
|
|
||||||
assertNotNull(root.findViewById<TextView>(R.id.tv_home_genre_creator_title_suffix))
|
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
|
@Test
|
||||||
@@ -512,4 +735,21 @@ class HomeMainFragmentLayoutTest {
|
|||||||
tags = setOf(AudioContentTag.First, AudioContentTag.Point, AudioContentTag.Free)
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user