feat(creator): FanTalk 탭 화면 연결을 추가한다

This commit is contained in:
2026-06-22 17:46:25 +09:00
parent 790e08f1b5
commit 40ef5710fb
5 changed files with 285 additions and 5 deletions

View File

@@ -63,6 +63,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioConte
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragment
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragment
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelScrollState
@@ -83,7 +84,8 @@ class CreatorChannelActivity :
CreatorChannelLiveFragment.Host,
CreatorChannelAudioFragment.Host,
CreatorChannelSeriesFragment.Host,
CreatorChannelCommunityFragment.Host {
CreatorChannelCommunityFragment.Host,
CreatorChannelFanTalkFragment.Host {
private val liveViewModel: LiveViewModel by inject()
private val myPageViewModel: MyPageViewModel by inject()
@@ -430,6 +432,9 @@ class CreatorChannelActivity :
CreatorChannelTab.Community.ordinal -> binding.viewPager.post {
findCommunityFragment()?.onCreatorChannelCommunityTabSelected()
}
CreatorChannelTab.FanTalk.ordinal -> binding.viewPager.post {
findFanTalkFragment()?.onCreatorChannelFanTalkTabSelected()
}
}
}
}
@@ -458,6 +463,11 @@ class CreatorChannelActivity :
findCommunityFragment()?.onCreatorChannelCommunityTabSelected()
}
}
if (binding.viewPager.currentItem == CreatorChannelTab.FanTalk.ordinal) {
binding.viewPager.post {
findFanTalkFragment()?.onCreatorChannelFanTalkTabSelected()
}
}
}
override fun onCreatorChannelFollowProgressChanged(inProgress: Boolean) {
@@ -532,6 +542,27 @@ class CreatorChannelActivity :
}
}
override fun onCreatorChannelFanTalkContentChanged() {
updateViewPagerHeight {
postCheckCreatorChannelCurrentTabNeedsMore()
}
}
override fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long) {
SodaDialog(
activity = this,
layoutInflater = layoutInflater,
title = getString(R.string.screen_user_profile_cheer_delete_title),
desc = getString(R.string.screen_user_profile_cheer_delete_desc),
confirmButtonTitle = getString(R.string.confirm_delete_title),
confirmButtonClick = {
findFanTalkFragment()?.onCreatorChannelFanTalkDeleteConfirmed(fanTalkId)
},
cancelButtonTitle = getString(R.string.cancel),
cancelButtonClick = {}
).show(screenWidth)
}
override fun onCreatorChannelCommunityOwnerMoreClicked(item: CreatorChannelCommunityPostUiModel) {
CreatorCommunityPostMenuBottomSheetDialog(
isFixed = item.isPinned,
@@ -623,6 +654,10 @@ class CreatorChannelActivity :
findCommunityFragment()?.onCreatorChannelCommunityRefreshRequested()
}
private fun refreshCreatorChannelFanTalk() {
findFanTalkFragment()?.onCreatorChannelFanTalkRefreshRequested()
}
private fun setupOwnerFabInsets() {
binding.viewPager.updatePadding(bottom = OWNER_FAB_CONTENT_BOTTOM_PADDING_DP.dpToPx().toInt())
}
@@ -817,12 +852,18 @@ class CreatorChannelActivity :
return supportFragmentManager.findFragmentByTag(fragmentTag) as? CreatorChannelCommunityFragment
}
private fun findFanTalkFragment(): CreatorChannelFanTalkFragment? {
val fragmentTag = "f${CreatorChannelTab.FanTalk.ordinal}"
return supportFragmentManager.findFragmentByTag(fragmentTag) as? CreatorChannelFanTalkFragment
}
private fun notifyCurrentCreatorChannelTabScrolledToBottom() {
when (binding.viewPager.currentItem) {
CreatorChannelTab.Live.ordinal -> findLiveFragment()?.onCreatorChannelLiveScrolledToBottom()
CreatorChannelTab.Audio.ordinal -> findAudioFragment()?.onCreatorChannelAudioScrolledToBottom()
CreatorChannelTab.Series.ordinal -> findSeriesFragment()?.onCreatorChannelSeriesScrolledToBottom()
CreatorChannelTab.Community.ordinal -> findCommunityFragment()?.onCreatorChannelCommunityScrolledToBottom()
CreatorChannelTab.FanTalk.ordinal -> findFanTalkFragment()?.onCreatorChannelFanTalkScrolledToBottom()
}
}
@@ -830,7 +871,8 @@ class CreatorChannelActivity :
return position == CreatorChannelTab.Live.ordinal ||
position == CreatorChannelTab.Audio.ordinal ||
position == CreatorChannelTab.Series.ordinal ||
position == CreatorChannelTab.Community.ordinal
position == CreatorChannelTab.Community.ordinal ||
position == CreatorChannelTab.FanTalk.ordinal
}
private fun ensureLoginAndAdultAuth(isAdult: Boolean, onAuthed: () -> Unit) {
@@ -928,6 +970,7 @@ class CreatorChannelActivity :
CreatorChannelTab.Live.ordinal -> findLiveFragment()?.onCreatorChannelLiveViewportHeightChanged(minHeight)
CreatorChannelTab.Audio.ordinal -> findAudioFragment()?.onCreatorChannelAudioViewportHeightChanged(minHeight)
CreatorChannelTab.Series.ordinal -> findSeriesFragment()?.onCreatorChannelSeriesViewportHeightChanged(minHeight)
CreatorChannelTab.FanTalk.ordinal -> findFanTalkFragment()?.onCreatorChannelFanTalkViewportHeightChanged(minHeight)
}
}

View File

@@ -5,6 +5,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragment
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragment
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragment
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragment
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragment
@@ -25,6 +26,7 @@ class CreatorChannelPagerAdapter(
CreatorChannelTab.Audio -> CreatorChannelAudioFragment.newInstance(creatorId)
CreatorChannelTab.Series -> CreatorChannelSeriesFragment.newInstance(creatorId)
CreatorChannelTab.Community -> CreatorChannelCommunityFragment.newInstance(creatorId)
CreatorChannelTab.FanTalk -> CreatorChannelFanTalkFragment.newInstance(creatorId)
else -> CreatorChannelPlaceholderFragment.newInstance(tab)
}
}

View File

@@ -416,7 +416,7 @@ class CreatorChannelActivitySourceTest {
assertFalse(source.contains("if (tab != CreatorChannelTab.Home) return"))
assertTrue(pagerAdapter.contains("CreatorChannelTab.Audio -> CreatorChannelAudioFragment.newInstance(creatorId)"))
assertTrue(pagerAdapter.contains("CreatorChannelTab.Series -> CreatorChannelSeriesFragment.newInstance(creatorId)"))
assertFalse(source.contains("CreatorChannelTab.FanTalk ->"))
assertTrue(pagerAdapter.contains("CreatorChannelTab.FanTalk -> CreatorChannelFanTalkFragment.newInstance(creatorId)"))
assertFalse(source.contains("CreatorChannelTab.Donation ->"))
}
@@ -487,6 +487,7 @@ class CreatorChannelActivitySourceTest {
assertFalse(source.contains("onClickDelete = {},"))
assertTrue(fragment.contains("fun onCreatorChannelCommunityRefreshRequested()"))
assertTrue(
fragment.contains(
"CreatorCommunityMediaPlayerManager(requireContext()) { listAdapter.notifyDataSetChanged() }"
)
@@ -495,6 +496,47 @@ class CreatorChannelActivitySourceTest {
assertTrue(fragment.contains("mediaPlayerManager?.stopContent()"))
}
@Test
fun `FanTalk tab source는 Fragment Host pagination height delete dialog를 Activity에 연결한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
).readText()
val adapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt"
).readText()
val fragment = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt"
).takeIf { it.exists() }?.readText().orEmpty()
assertTrue(adapter.contains("CreatorChannelFanTalkFragment.newInstance(creatorId)"))
assertTrue(source.contains("CreatorChannelFanTalkFragment.Host"))
assertTrue(
source.contains(
"import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragment"
)
)
assertTrue(source.contains("private fun findFanTalkFragment(): CreatorChannelFanTalkFragment?"))
assertTrue(source.contains("CreatorChannelTab.FanTalk.ordinal -> binding.viewPager.post"))
assertTrue(source.contains("findFanTalkFragment()?.onCreatorChannelFanTalkTabSelected()"))
assertTrue(source.contains("if (binding.viewPager.currentItem == CreatorChannelTab.FanTalk.ordinal)"))
assertTrue(source.contains("findFanTalkFragment()?.onCreatorChannelFanTalkScrolledToBottom()"))
assertTrue(source.contains("findFanTalkFragment()?.onCreatorChannelFanTalkViewportHeightChanged(minHeight)"))
assertTrue(source.contains("position == CreatorChannelTab.FanTalk.ordinal"))
assertTrue(source.contains("override fun onCreatorChannelFanTalkContentChanged()"))
assertTrue(source.contains("override fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long)"))
assertTrue(source.contains("SodaDialog("))
assertTrue(source.contains("R.string.screen_user_profile_cheer_delete_title"))
assertTrue(source.contains("R.string.screen_user_profile_cheer_delete_desc"))
assertTrue(source.contains("R.string.confirm_delete_title"))
assertTrue(source.contains("R.string.cancel"))
assertTrue(source.contains("findFanTalkFragment()?.onCreatorChannelFanTalkDeleteConfirmed(fanTalkId)"))
assertTrue(source.contains("findFanTalkFragment()?.onCreatorChannelFanTalkRefreshRequested()"))
assertTrue(fragment.contains("interface Host"))
assertTrue(fragment.contains("fun isCreatorChannelOwner(): Boolean"))
assertTrue(fragment.contains("fun onCreatorChannelFanTalkContentChanged()"))
assertTrue(fragment.contains("fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long)"))
}
@Test
fun `section adapter source는 활동 지표를 행 단위 resource label로 표시한다`() {
val adapter = projectFile(

View File

@@ -4,6 +4,7 @@ import android.app.Application
import androidx.fragment.app.FragmentActivity
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragment
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragment
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragment
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragment
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragment
@@ -20,7 +21,7 @@ import org.robolectric.annotation.Config
class CreatorChannelPagerAdapterTest {
@Test
fun `createFragment는 Home Live Audio Series Community를 실제 Fragment로 생성하고 나머지는 placeholder를 유지한다`() {
fun `createFragment는 Home Live Audio Series Community FanTalk를 실제 Fragment로 생성하고 나머지는 placeholder를 유지한다`() {
val activity = Robolectric.buildActivity(FragmentActivity::class.java).setup().get()
val adapter = CreatorChannelPagerAdapter(activity, creatorId = 123L)
@@ -29,13 +30,15 @@ class CreatorChannelPagerAdapterTest {
assertTrue(adapter.createFragment(CreatorChannelTab.Audio.ordinal) is CreatorChannelAudioFragment)
assertTrue(adapter.createFragment(CreatorChannelTab.Series.ordinal) is CreatorChannelSeriesFragment)
assertTrue(adapter.createFragment(CreatorChannelTab.Community.ordinal) is CreatorChannelCommunityFragment)
assertTrue(adapter.createFragment(CreatorChannelTab.FanTalk.ordinal) is CreatorChannelFanTalkFragment)
CreatorChannelTab.entries
.filterNot {
it == CreatorChannelTab.Home ||
it == CreatorChannelTab.Live ||
it == CreatorChannelTab.Audio ||
it == CreatorChannelTab.Series ||
it == CreatorChannelTab.Community
it == CreatorChannelTab.Community ||
it == CreatorChannelTab.FanTalk
}
.forEach { tab ->
assertTrue(adapter.createFragment(tab.ordinal) is CreatorChannelPlaceholderFragment)

View File

@@ -0,0 +1,190 @@
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk
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.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 CreatorChannelFanTalkFragmentLayoutTest {
@Test
fun `팬톡 fragment layout은 count list empty error retry floating write를 제공하고 sort UI는 제외한다`() {
val root = inflateView(R.layout.fragment_creator_channel_fantalk)
val layout = projectFile("app/src/main/res/layout/fragment_creator_channel_fantalk.xml").readText()
val countBar = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_fantalk_count_bar))
val fanTalkList = requireNotNull(root.findViewById<RecyclerView>(R.id.rv_creator_channel_fantalk))
val emptyContainer = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_fantalk_empty))
val emptyMessage = requireNotNull(root.findViewById<TextView>(R.id.tv_creator_channel_fantalk_empty_message))
val emptyButton = requireNotNull(root.findViewById<View>(R.id.layout_creator_channel_fantalk_empty_write_button))
val errorMessage = requireNotNull(root.findViewById<TextView>(R.id.tv_creator_channel_fantalk_error_message))
val retryButton = requireNotNull(root.findViewById<TextView>(R.id.btn_creator_channel_fantalk_retry))
val floatingWriteButton = requireNotNull(root.findViewById<View>(R.id.btn_creator_channel_fantalk_write))
assertSame(root, countBar.parent)
assertSame(root, fanTalkList.parent)
assertSame(root, emptyContainer.parent)
assertSame(emptyContainer, emptyMessage.parent)
assertSame(emptyContainer, emptyButton.parent)
assertSame(root, errorMessage.parent)
assertSame(root, retryButton.parent)
assertSame(root, floatingWriteButton.parent)
assertEquals(false, fanTalkList.clipToPadding)
assertNotNull(countBar.findViewById<TextView>(R.id.tv_creator_channel_fantalk_total_label))
assertNotNull(countBar.findViewById<TextView>(R.id.tv_creator_channel_fantalk_total_count))
assertFalse(layout.contains("sort"))
assertFalse(layout.contains("정렬"))
assertFalse(layout.contains("iv_creator_channel_fantalk_sort"))
assertTrue(layout.contains("android:text=\"@string/creator_channel_fantalk_all_label\""))
assertTrue(layout.contains("android:text=\"@string/creator_channel_fantalk_empty_message\""))
assertTrue(layout.contains("android:gravity=\"center\""))
assertTrue(layout.contains("android:layout_height=\"0dp\""))
assertTrue(layout.contains("app:layout_constraintBottom_toBottomOf=\"parent\""))
assertTrue(layout.contains("android:text=\"@string/creator_channel_fantalk_support_action\""))
assertTrue(layout.contains("tools:listitem=\"@layout/item_creator_channel_fantalk\""))
assertTrue(layout.contains("@drawable/ic_new_fantalk_plus"))
assertLocaleString("values/strings.xml", "아직 응원이 없습니다.\\n처음으로 크리에이터를 응원해 보세요!")
assertLocaleString("values-en/strings.xml", "No cheers yet.\\nBe the first to cheer for the creator!")
assertLocaleString("values-ja/strings.xml", "まだ応援がありません。\\n最初にクリエイターを応援してみましょう")
}
@Test
fun `팬톡 item layout은 원글 action과 reply container를 제공한다`() {
val item = inflateView(R.layout.item_creator_channel_fantalk)
val itemLayout = projectFile("app/src/main/res/layout/item_creator_channel_fantalk.xml").readText()
val replyBackground = projectFile(
"app/src/main/res/drawable/bg_creator_channel_fantalk_reply.xml"
).readText()
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_fantalk_profile))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_fantalk_nickname))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_fantalk_time))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_fantalk_content))
assertNotNull(item.findViewById<TextView>(R.id.tv_creator_channel_fantalk_report))
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_fantalk_more))
assertNotNull(item.findViewById<View>(R.id.view_creator_channel_fantalk_reply_connector))
assertNotNull(item.findViewById<View>(R.id.layout_creator_channel_fantalk_reply))
assertNotNull(item.findViewById<ImageView>(R.id.iv_creator_channel_fantalk_reply_profile))
assertTrue(itemLayout.contains("@drawable/ic_new_more"))
assertTrue(itemLayout.contains("android:text=\"@string/creator_channel_fantalk_report\""))
assertTrue(itemLayout.contains("android:layout_width=\"42dp\""))
assertTrue(itemLayout.contains("android:layout_height=\"42dp\""))
assertTrue(itemLayout.contains("android:layout_width=\"20dp\""))
assertTrue(itemLayout.contains("android:layout_height=\"20dp\""))
assertTrue(itemLayout.contains("@+id/barrier_creator_channel_fantalk_action_start"))
assertTrue(itemLayout.contains("app:barrierDirection=\"start\""))
assertTrue(
itemLayout.contains(
"app:constraint_referenced_ids=\"tv_creator_channel_fantalk_report,iv_creator_channel_fantalk_more\""
)
)
assertTrue(
itemLayout.contains(
"app:layout_constraintEnd_toStartOf=\"@id/barrier_creator_channel_fantalk_action_start\""
)
)
assertTrue(itemLayout.contains("@drawable/bg_creator_channel_fantalk_reply"))
assertTrue(replyBackground.contains("@color/gray_900"))
}
@Test
fun `팬톡 more popup layout은 수정 삭제 TextView를 제공한다`() {
val popup = inflateView(R.layout.view_creator_channel_fantalk_more_popup)
val popupLayout = projectFile("app/src/main/res/layout/view_creator_channel_fantalk_more_popup.xml").readText()
assertNotNull(popup.findViewById<TextView>(R.id.tv_creator_channel_fantalk_more_edit))
assertNotNull(popup.findViewById<TextView>(R.id.tv_creator_channel_fantalk_more_delete))
assertTrue(popupLayout.contains("android:text=\"@string/creator_channel_fantalk_edit\""))
assertTrue(popupLayout.contains("android:text=\"@string/creator_channel_fantalk_delete\""))
assertTrue(popupLayout.contains("@drawable/bg_creator_channel_sort_popup"))
}
@Test
fun `팬톡 adapter와 popup source는 Phase 4 바인딩 계약을 포함한다`() {
val adapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkAdapter.kt"
).readText()
val popup = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkMorePopup.kt"
).readText()
assertTrue(adapter.contains("ItemCreatorChannelFantalkBinding"))
assertTrue(adapter.contains("fun submitItems(items: List<CreatorChannelFanTalkUiModel>)"))
assertTrue(adapter.contains("notifyDataSetChanged()"))
assertTrue(adapter.contains("CircleCropTransformation()"))
assertTrue(adapter.contains("R.drawable.ic_placeholder_profile"))
assertTrue(adapter.contains("CreatorChannelFanTalkRightAction.Report"))
assertTrue(adapter.contains("CreatorChannelFanTalkRightAction.OwnerMore"))
assertFalse(adapter.contains("root.setOnClickListener"))
assertTrue(popup.contains("PopupWindow(anchor.context)"))
assertTrue(popup.contains("tvCreatorChannelFantalkMoreEdit.isVisible = showEdit"))
assertTrue(popup.contains("tvCreatorChannelFantalkMoreDelete.isVisible = showDelete"))
assertTrue(popup.contains("onDeleteClick(fanTalkId)"))
}
@Test
fun `팬톡 fragment source는 Phase 5 상태 신고 삭제 Host 계약을 포함한다`() {
val fragment = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt"
).readText()
val adapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkAdapter.kt"
).readText()
assertTrue(fragment.contains("BaseFragment<FragmentCreatorChannelFantalkBinding>"))
assertTrue(fragment.contains("private val viewModel: CreatorChannelFanTalkViewModel by viewModel()"))
assertTrue(fragment.contains("layoutManager = LinearLayoutManager(requireContext())"))
assertTrue(fragment.contains("onCreatorChannelFanTalkTabSelected()"))
assertTrue(fragment.contains("viewModel.loadFanTalks(creatorId, isOwner = host.isCreatorChannelOwner())"))
assertTrue(fragment.contains("onCreatorChannelFanTalkScrolledToBottom()"))
assertTrue(fragment.contains("viewModel.loadMore()"))
assertTrue(fragment.contains("onCreatorChannelFanTalkRefreshRequested()"))
assertTrue(fragment.contains("viewModel.refreshFanTalks()"))
assertTrue(fragment.contains("onCreatorChannelFanTalkViewportHeightChanged(minHeight: Int)"))
assertTrue(fragment.contains("binding.root.minimumHeight = minHeight"))
assertTrue(fragment.contains("onCreatorChannelFanTalkDeleteConfirmed(fanTalkId: Long)"))
assertTrue(fragment.contains("viewModel.deleteFanTalk(fanTalkId)"))
assertTrue(fragment.contains("CheersReportDialog(requireActivity(), layoutInflater)"))
assertTrue(fragment.contains("R.string.screen_user_profile_fantalk_report_reason_required"))
assertTrue(fragment.contains("viewModel.reportFanTalk(item.fanTalkId, reason)"))
assertTrue(fragment.contains("CreatorChannelFanTalkMorePopup("))
assertTrue(fragment.contains("onDeleteClick = host::onCreatorChannelFanTalkDeleteClicked"))
assertTrue(fragment.contains("fun onCreatorChannelFanTalkContentChanged()"))
assertTrue(fragment.contains("fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long)"))
assertTrue(adapter.contains("private val onReportClick: (CreatorChannelFanTalkUiModel) -> Unit"))
assertTrue(adapter.contains("tvCreatorChannelFantalkReport.setOnClickListener { onReportClick(item) }"))
}
private fun inflateView(layoutResId: Int): View {
val context = ApplicationProvider.getApplicationContext<Context>()
return LayoutInflater.from(context).inflate(layoutResId, null, false)
}
private fun projectFile(path: String): File = File(projectRoot(), path)
private fun assertLocaleString(path: String, expectedValue: String) {
val strings = projectFile("app/src/main/res/$path").readText()
assertTrue(strings.contains("<string name=\"creator_channel_fantalk_empty_message\">$expectedValue</string>"))
}
private fun projectRoot(): File {
return generateSequence(File(System.getProperty("user.dir") ?: ".").absoluteFile) { it.parentFile }
.first { File(it, "settings.gradle").exists() }
}
}