test(creator): 커뮤니티 탭 레이아웃 검증을 추가한다

This commit is contained in:
2026-06-22 00:36:49 +09:00
parent 318944fbfe
commit 3e4c00fee8
2 changed files with 300 additions and 5 deletions

View File

@@ -0,0 +1,232 @@
package kr.co.vividnext.sodalive.v2.creator.channel.community
import android.app.Application
import android.content.Context
import android.view.LayoutInflater
import android.view.View
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
import java.io.File
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28], application = Application::class)
class CreatorChannelCommunityFragmentLayoutTest {
@Test
fun `커뮤니티 fragment layout은 sort list empty error retry를 제공한다`() {
val root = inflateView(R.layout.fragment_creator_channel_community)
val layout = projectFile("app/src/main/res/layout/fragment_creator_channel_community.xml").readText()
val sortBar = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_community_sort_bar))
val communityList = requireNotNull(root.findViewById<RecyclerView>(R.id.rv_creator_channel_community))
val emptyContainer = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_community_empty))
val emptyMessage = requireNotNull(root.findViewById<TextView>(R.id.tv_creator_channel_community_empty_message))
val errorMessage = requireNotNull(root.findViewById<TextView>(R.id.tv_creator_channel_community_error_message))
val retryButton = requireNotNull(root.findViewById<TextView>(R.id.btn_creator_channel_community_retry))
assertSame(root, sortBar.parent)
assertSame(root, communityList.parent)
assertSame(root, emptyContainer.parent)
assertSame(emptyContainer, emptyMessage.parent)
assertSame(root, errorMessage.parent)
assertSame(root, retryButton.parent)
assertEquals(false, communityList.clipToPadding)
assertTrue(layout.contains("android:background=\"@color/black\""))
assertTrue(layout.contains("android:text=\"@string/creator_channel_community_empty_message\""))
assertTrue(layout.contains("android:text=\"@string/creator_channel_community_error_message\""))
assertTrue(layout.contains("tools:listitem=\"@layout/item_creator_channel_community_list\""))
}
@Test
fun `커뮤니티 sort bar는 전체 count 보기 방식 label icon을 제공한다`() {
val root = inflateView(R.layout.fragment_creator_channel_community)
val sortBar = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_community_sort_bar))
assertNotNull(sortBar.findViewById<TextView>(R.id.tv_creator_channel_community_total_label))
assertNotNull(sortBar.findViewById<TextView>(R.id.tv_creator_channel_community_total_count))
assertNotNull(sortBar.findViewById<View>(R.id.layout_creator_channel_community_view_mode_button))
assertNotNull(sortBar.findViewById<TextView>(R.id.tv_creator_channel_community_view_mode_label))
assertNotNull(sortBar.findViewById<ImageView>(R.id.iv_creator_channel_community_view_mode))
}
@Test
fun `커뮤니티 list item layout은 프로필 본문 이미지 잠금 재생 반응 owner 영역을 제공한다`() {
val item = inflateView(R.layout.item_creator_channel_community_list)
val itemLayout = projectFile("app/src/main/res/layout/item_creator_channel_community_list.xml").readText()
val imageContainer = requireNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_image_container))
val lockIcon = requireNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_list_lock))
val lockedPrice = requireNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_locked_price))
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_list_profile))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_nickname))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_time))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_notice))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_body))
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_list_image))
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_locked_overlay))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_locked_price))
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_list_play))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_comment_count))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_like_count))
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_community_list_top_actions))
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_list_owner_more))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_list_top_price))
assertSame(imageContainer, (lockIcon.parent as View).parent)
assertSame(imageContainer, (lockedPrice.parent as View).parent)
assertSame(
item.findViewById<View>(R.id.layout_creator_channel_community_list_top_actions),
item.findViewById<TextView>(R.id.tv_creator_channel_community_list_top_price).parent
)
assertTrue(itemLayout.contains("android:id=\"@+id/layout_creator_channel_community_list_locked_overlay\""))
assertTrue(itemLayout.contains("android:id=\"@+id/tv_creator_channel_community_list_locked_price\""))
assertTrue(itemLayout.contains("android:background=\"@drawable/bg_creator_channel_community_price\""))
assertTrue(itemLayout.contains("android:drawableStart=\"@drawable/ic_bar_cash\""))
assertTrue(itemLayout.contains("android:id=\"@+id/iv_creator_channel_community_list_play\""))
}
@Test
fun `커뮤니티 grid item layout은 이미지 본문 잠금 가격 공지와 정사각 root 계약을 제공한다`() {
val item = inflateView(R.layout.item_creator_channel_community_grid)
val itemLayout = projectFile("app/src/main/res/layout/item_creator_channel_community_grid.xml").readText()
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_community_grid_image))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_grid_text_preview))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_grid_lock_price))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_community_grid_notice))
assertTrue(itemLayout.contains("android:id=\"@+id/layout_creator_channel_community_grid_root\""))
assertTrue(itemLayout.contains("app:layout_constraintDimensionRatio=\"1:1\"") || itemLayout.contains("Square"))
}
@Test
fun `커뮤니티 문자열은 한국어 영어 일본어와 기존 보기 방식 label에 존재한다`() {
val ko = projectFile("app/src/main/res/values/strings.xml").readText()
val en = projectFile("app/src/main/res/values-en/strings.xml").readText()
val ja = projectFile("app/src/main/res/values-ja/strings.xml").readText()
listOf(ko, en, ja).forEach { strings ->
assertTrue(strings.contains("name=\"creator_channel_community_empty_message\""))
assertTrue(strings.contains("name=\"creator_channel_community_error_message\""))
assertTrue(strings.contains("name=\"creator_channel_community_retry_button\""))
assertTrue(strings.contains("name=\"creator_channel_community_notice\""))
assertTrue(strings.contains("name=\"creator_channel_community_view_mode_list\""))
assertTrue(strings.contains("name=\"creator_channel_community_view_mode_grid\""))
}
}
@Test
fun `커뮤니티 fragment source는 pagination view mode owner padding stopContent 계약을 사용한다`() {
val fragment = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt"
).readText()
assertTrue(fragment.contains("BaseFragment<FragmentCreatorChannelCommunityBinding>"))
assertTrue(fragment.contains("private val viewModel: CreatorChannelCommunityViewModel by viewModel()"))
assertTrue(fragment.contains("viewModel.communityStateLiveData.observe(viewLifecycleOwner)"))
assertTrue(fragment.contains("viewModel.loadCommunity(creatorId, isOwner = host.isCreatorChannelOwner())"))
assertTrue(fragment.contains("viewModel.loadMore()"))
assertTrue(fragment.contains("viewModel.retryCommunity()"))
assertTrue(fragment.contains("viewModel.toggleViewMode()"))
assertTrue(fragment.contains("LinearLayoutManager"))
assertTrue(
fragment.contains("GridLayoutManager(requireContext(), 3") ||
fragment.contains("GridLayoutManager(context, 3")
)
assertTrue(fragment.contains("viewModel.consumePaginationErrorMessage()"))
assertTrue(fragment.contains("applyOwnerCtaPadding"))
assertTrue(fragment.contains("pauseContent"))
assertTrue(fragment.contains("stopContent"))
}
@Test
fun `커뮤니티 adapters source는 binding 상태 표시와 root click navigation 배제를 보장한다`() {
val listAdapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt"
).readText()
val gridAdapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt"
).readText()
assertTrue(listAdapter.contains("ItemCreatorChannelCommunityListBinding"))
assertTrue(gridAdapter.contains("ItemCreatorChannelCommunityGridBinding"))
assertTrue(listAdapter.contains("submitItems"))
assertTrue(gridAdapter.contains("submitItems"))
assertTrue(listAdapter.contains("showComment") && listAdapter.contains("isVisible = item.showComment"))
assertTrue(listAdapter.contains("isLocked"))
assertTrue(gridAdapter.contains("isLocked"))
assertTrue(listAdapter.contains("showPlayButton"))
assertTrue(listAdapter.contains("showOwnerMore"))
assertTrue(listAdapter.contains("isPlayingContent"))
assertTrue(listAdapter.contains("R.drawable.ic_player_pause"))
assertTrue(listAdapter.contains("R.drawable.ic_new_player_play"))
assertTrue(listAdapter.contains("onOwnerMoreClick(item)"))
assertTrue(listAdapter.contains("tvCreatorChannelCommunityListLockedPrice.isVisible = item.isLocked"))
assertTrue(listAdapter.contains("tvCreatorChannelCommunityListTopPrice.isVisible = item.showOwnerTopPrice"))
assertTrue(!listAdapter.contains("item.isLocked || item.showOwnerTopPrice"))
assertTrue(!listAdapter.contains("root.setOnClickListener"))
assertTrue(!gridAdapter.contains("root.setOnClickListener"))
}
@Test
fun `커뮤니티 media player source는 prepare 완료 전 pause stop 호출을 보호한다`() {
val playerManager = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/player/" +
"CreatorCommunityMediaPlayerManager.kt"
).readText()
assertTrue(playerManager.contains("private var isPrepared: Boolean = false"))
assertTrue(playerManager.contains("if (isPrepared)"))
assertTrue(playerManager.contains("isPrepared = true"))
assertTrue(playerManager.contains("isPrepared = false"))
assertTrue(playerManager.contains("mediaPlayer?.pause()"))
assertTrue(playerManager.contains("it.stop()"))
}
@Test
fun `커뮤니티 adapters source는 잠금 항목과 text preview에서 imageUrl load를 호출하지 않는다`() {
val listAdapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt"
).readText()
val gridAdapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt"
).readText()
assertTrue(listAdapter.contains("val visibleImageUrl = item.imageUrl.takeUnless { item.isLocked }"))
assertTrue(listAdapter.contains("ivCreatorChannelCommunityListImage.setImageDrawable(null)"))
assertTrue(gridAdapter.contains("val visibleImageUrl = item.imageUrl.takeIf"))
assertTrue(gridAdapter.contains("item.imageMode == CreatorChannelCommunityImageMode.Image"))
assertTrue(gridAdapter.contains("ivCreatorChannelCommunityGridImage.setImageDrawable(null)"))
}
@Test
fun `커뮤니티 grid adapter source는 margin을 제외한 3열 정사각 크기를 계산한다`() {
val gridAdapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt"
).readText()
assertTrue(gridAdapter.contains("leftMargin + rightMargin"))
assertTrue(gridAdapter.contains("availableWidth - totalHorizontalMargins"))
assertTrue(gridAdapter.contains("coerceAtLeast(0)"))
}
private fun inflateView(layoutResId: Int): View {
val context = ApplicationProvider.getApplicationContext<Context>()
return LayoutInflater.from(context).inflate(layoutResId, null, false)
}
private fun projectFile(relativePath: String): File {
val candidates = listOf(File(relativePath), File("../$relativePath"))
return candidates.firstOrNull { it.exists() }
?: error("Project file not found: $relativePath")
}
}