feat(creator): 라이브 탭 레이아웃을 추가한다
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragment
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], application = Application::class)
|
||||
class CreatorChannelPagerAdapterTest {
|
||||
|
||||
@Test
|
||||
fun `createFragment는 Home과 Live를 실제 Fragment로 생성하고 나머지는 placeholder를 유지한다`() {
|
||||
val activity = Robolectric.buildActivity(FragmentActivity::class.java).setup().get()
|
||||
val adapter = CreatorChannelPagerAdapter(activity, creatorId = 123L)
|
||||
|
||||
assertTrue(adapter.createFragment(CreatorChannelTab.Home.ordinal) is CreatorChannelHomeFragment)
|
||||
assertTrue(adapter.createFragment(CreatorChannelTab.Live.ordinal) is CreatorChannelLiveFragment)
|
||||
CreatorChannelTab.entries
|
||||
.filterNot { it == CreatorChannelTab.Home || it == CreatorChannelTab.Live }
|
||||
.forEach { tab ->
|
||||
assertTrue(adapter.createFragment(tab.ordinal) is CreatorChannelPlaceholderFragment)
|
||||
}
|
||||
assertEquals(CreatorChannelTab.entries.size, adapter.itemCount)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.live
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
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.assertFalse
|
||||
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 CreatorChannelLiveFragmentLayoutTest {
|
||||
|
||||
@Test
|
||||
fun `라이브 fragment layout은 sort current live list empty error owner CTA를 제공한다`() {
|
||||
val root = inflateView(R.layout.fragment_creator_channel_live)
|
||||
val sortBar = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_live_sort_bar))
|
||||
val currentLiveCard = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_live_current_card))
|
||||
val replayList = requireNotNull(root.findViewById<RecyclerView>(R.id.rv_creator_channel_live_replays))
|
||||
val emptyMessage = requireNotNull(root.findViewById<TextView>(R.id.tv_creator_channel_live_empty_message))
|
||||
val errorMessage = requireNotNull(root.findViewById<TextView>(R.id.tv_creator_channel_live_error_message))
|
||||
val retryButton = requireNotNull(root.findViewById<TextView>(R.id.btn_creator_channel_live_retry))
|
||||
val ownerCta = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_live_owner_cta))
|
||||
|
||||
assertSame(root, sortBar.parent)
|
||||
assertSame(root, replayList.parent)
|
||||
assertSame(root, emptyMessage.parent)
|
||||
assertSame(root, errorMessage.parent)
|
||||
assertSame(root, retryButton.parent)
|
||||
assertSame(root, ownerCta.parent)
|
||||
assertNotNull(currentLiveCard.findViewById<TextView>(R.id.tv_creator_channel_live_current_title))
|
||||
assertEquals(false, replayList.clipToPadding)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `라이브 sort bar는 전체 count 정렬 label sort icon을 제공한다`() {
|
||||
val root = inflateView(R.layout.fragment_creator_channel_live)
|
||||
val sortBar = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_live_sort_bar))
|
||||
|
||||
assertNotNull(sortBar.findViewById<TextView>(R.id.tv_creator_channel_live_total_label))
|
||||
assertNotNull(sortBar.findViewById<TextView>(R.id.tv_creator_channel_live_total_count))
|
||||
assertNotNull(sortBar.findViewById<TextView>(R.id.tv_creator_channel_live_sort_label))
|
||||
assertNotNull(sortBar.findViewById<ImageView>(R.id.iv_creator_channel_live_sort))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `라이브 다시듣기 item layout은 썸네일 tag title duration action 영역을 제공한다`() {
|
||||
val item = inflateView(R.layout.item_creator_channel_live_replay)
|
||||
|
||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_live_replay_thumbnail))
|
||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_live_replay_adult_badge))
|
||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_live_replay_original_tag))
|
||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_live_replay_first_tag))
|
||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_live_replay_point_tag))
|
||||
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_live_replay_free_tag))
|
||||
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_live_replay_title))
|
||||
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_live_replay_duration))
|
||||
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_live_replay_action))
|
||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_live_replay_play))
|
||||
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_live_replay_can))
|
||||
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_live_replay_action_text))
|
||||
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_live_replay_action_text))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `라이브 layout은 가격 영역을 유연한 너비와 bar cash icon으로 제공한다`() {
|
||||
val fragmentLayout = projectFile("app/src/main/res/layout/fragment_creator_channel_live.xml").readText()
|
||||
val itemLayout = projectFile("app/src/main/res/layout/item_creator_channel_live_replay.xml").readText()
|
||||
val root = inflateView(R.layout.fragment_creator_channel_live)
|
||||
val item = inflateView(R.layout.item_creator_channel_live_replay)
|
||||
val currentPrice = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_live_current_price))
|
||||
val currentPriceCash = requireNotNull(root.findViewById<ImageView>(R.id.iv_creator_channel_live_current_price_cash))
|
||||
val replayAction = requireNotNull(item.findViewById<View>(R.id.layout_creator_channel_live_replay_action))
|
||||
val replayPlay = requireNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_live_replay_play))
|
||||
val actionText = requireNotNull(item.findViewById<View>(R.id.layout_creator_channel_live_replay_action_text))
|
||||
val adultBadge = requireNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_live_replay_adult_badge))
|
||||
val adultBadgeParams = adultBadge.layoutParams as ViewGroup.MarginLayoutParams
|
||||
|
||||
assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, currentPrice.layoutParams.width)
|
||||
assertNotNull(currentPriceCash)
|
||||
assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, replayAction.layoutParams.width)
|
||||
assertEquals(dp(28), replayPlay.layoutParams.width)
|
||||
assertEquals(dp(28), replayPlay.layoutParams.height)
|
||||
assertEquals(dp(6), replayPlay.paddingStart)
|
||||
assertEquals(dp(8), (actionText.layoutParams as ViewGroup.MarginLayoutParams).topMargin)
|
||||
assertEquals(dp(6), adultBadgeParams.topMargin)
|
||||
assertEquals(dp(6), adultBadgeParams.marginEnd)
|
||||
assertTrue(fragmentLayout.contains("@drawable/ic_bar_cash"))
|
||||
assertTrue(itemLayout.contains("@drawable/ic_bar_cash"))
|
||||
assertTrue(itemLayout.contains("@drawable/bg_creator_channel_live_replay_play"))
|
||||
assertTrue(itemLayout.contains("android:orientation=\"vertical\""))
|
||||
assertFalse(fragmentLayout.contains("@drawable/ic_can"))
|
||||
assertFalse(itemLayout.contains("@drawable/ic_can"))
|
||||
assertFalse(
|
||||
fragmentLayout.contains(
|
||||
"android:id=\"@+id/layout_creator_channel_live_current_price\"\n" +
|
||||
" android:layout_width=\"60dp\""
|
||||
)
|
||||
)
|
||||
assertFalse(
|
||||
itemLayout.contains(
|
||||
"android:id=\"@+id/layout_creator_channel_live_replay_action\"\n" +
|
||||
" android:layout_width=\"60dp\""
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `라이브 fragment와 adapter source는 필수 drawable과 retry loadMore click 연결을 포함한다`() {
|
||||
val fragment = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt"
|
||||
).readText()
|
||||
val adapter = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveReplayAdapter.kt"
|
||||
).readText()
|
||||
val layout = projectFile("app/src/main/res/layout/fragment_creator_channel_live.xml").readText()
|
||||
val eagerLoadOnViewCreated = """
|
||||
onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupReplayList()
|
||||
setupClickListeners()
|
||||
observeViewModel()
|
||||
if (creatorId > 0L)
|
||||
""".trimIndent()
|
||||
|
||||
assertTrue(layout.contains("android:layout_height=\"match_parent\""))
|
||||
assertTrue(fragment.contains("R.drawable.ic_new_sort"))
|
||||
assertTrue(fragment.contains("super.onViewCreated(view, savedInstanceState)\n bindLoading()"))
|
||||
assertTrue(fragment.contains("retryLive()"))
|
||||
assertTrue(fragment.contains("fun onCreatorChannelLiveTabSelected()"))
|
||||
assertTrue(fragment.contains("viewModel.loadLive(creatorId)"))
|
||||
assertFalse(fragment.contains(eagerLoadOnViewCreated))
|
||||
assertTrue(fragment.contains("fun onCreatorChannelLiveScrolledToBottom()"))
|
||||
assertTrue(fragment.contains("viewModel.loadMore()"))
|
||||
assertTrue(fragment.contains("host.onCreatorChannelLiveContentChanged()"))
|
||||
assertTrue(fragment.contains("notifyContentChangedIfLayoutChanged(state)"))
|
||||
assertTrue(fragment.contains("if (contentLayoutKey == lastContentLayoutKey) return"))
|
||||
assertTrue(fragment.contains("viewModel.consumePaginationErrorMessage()"))
|
||||
assertTrue(fragment.contains("bindEmpty() = with(binding)"))
|
||||
assertTrue(fragment.contains("bindError(state: CreatorChannelLiveUiState.Error) = with(binding)"))
|
||||
assertTrue(fragment.contains("formatCreatorChannelLiveDateTime(live.beginDateTimeUtc)"))
|
||||
assertTrue(fragment.contains("ivCreatorChannelLiveCurrentPriceCash.isVisible = live.price > 0"))
|
||||
assertTrue(fragment.contains("R.string.audio_content_tag_free"))
|
||||
assertTrue(fragment.contains("onCreatorChannelCurrentLiveClicked"))
|
||||
assertTrue(fragment.contains("onCreatorChannelLiveContentChanged"))
|
||||
assertFalse(fragment.contains("addOnScrollListener(object : RecyclerView.OnScrollListener()"))
|
||||
assertTrue(adapter.contains("R.drawable.ic_new_shield_small"))
|
||||
assertTrue(adapter.contains("R.drawable.ic_new_player_play"))
|
||||
assertTrue(adapter.contains("ivCreatorChannelLiveReplayPlay.isVisible = true"))
|
||||
assertTrue(adapter.contains("layoutCreatorChannelLiveReplayActionText.isVisible = false"))
|
||||
assertTrue(adapter.contains("layoutCreatorChannelLiveReplayActionText.isVisible = true"))
|
||||
}
|
||||
|
||||
private fun inflateView(layoutResId: Int): View {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
return LayoutInflater.from(context).inflate(layoutResId, null, false)
|
||||
}
|
||||
|
||||
private fun dp(value: Int): Int {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
return (value * context.resources.displayMetrics.density).toInt()
|
||||
}
|
||||
|
||||
private fun projectFile(relativePath: String): File {
|
||||
val candidates = listOf(File(relativePath), File("../$relativePath"))
|
||||
return candidates.firstOrNull { it.exists() }
|
||||
?: error("Project file not found: $relativePath")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user