feat(creator): FanTalk 탭 UI 모델 매핑을 추가한다

This commit is contained in:
2026-06-22 16:38:00 +09:00
parent 25320e283d
commit b7eba4c99a
3 changed files with 212 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model
import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkResponse
fun List<CreatorChannelFanTalkResponse>.toFanTalkUiModels(
relativeTimeTextFormatter: UtcRelativeTimeTextFormatter,
isOwner: Boolean,
currentUserId: Long
): List<CreatorChannelFanTalkUiModel> = map {
it.toFanTalkUiModel(relativeTimeTextFormatter, isOwner, currentUserId)
}
private fun CreatorChannelFanTalkResponse.toFanTalkUiModel(
relativeTimeTextFormatter: UtcRelativeTimeTextFormatter,
isOwner: Boolean,
currentUserId: Long
) = CreatorChannelFanTalkUiModel(
fanTalkId = fanTalkId,
writerId = writerId,
writerNickname = writerNickname,
writerProfileImageUrl = writerProfileImageUrl,
content = content,
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
reply = creatorReplies.firstOrNull()?.toReplyUiModel(relativeTimeTextFormatter),
rightAction = toRightAction(isOwner = isOwner, currentUserId = currentUserId)
)
private fun CreatorChannelFanTalkReplyResponse.toReplyUiModel(
relativeTimeTextFormatter: UtcRelativeTimeTextFormatter
) = CreatorChannelFanTalkReplyUiModel(
fanTalkId = fanTalkId,
writerId = writerId,
writerNickname = writerNickname,
writerProfileImageUrl = writerProfileImageUrl,
content = content,
createdAtText = relativeTimeTextFormatter.format(createdAtUtc)
)
private fun CreatorChannelFanTalkResponse.toRightAction(
isOwner: Boolean,
currentUserId: Long
): CreatorChannelFanTalkRightAction = when {
writerId == currentUserId -> CreatorChannelFanTalkRightAction.OwnerMore(showEdit = true, showDelete = true)
isOwner -> CreatorChannelFanTalkRightAction.OwnerMore(showEdit = false, showDelete = true)
else -> CreatorChannelFanTalkRightAction.Report
}

View File

@@ -0,0 +1,29 @@
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model
data class CreatorChannelFanTalkUiModel(
val fanTalkId: Long,
val writerId: Long,
val writerNickname: String,
val writerProfileImageUrl: String,
val content: String,
val createdAtText: String,
val reply: CreatorChannelFanTalkReplyUiModel?,
val rightAction: CreatorChannelFanTalkRightAction
)
data class CreatorChannelFanTalkReplyUiModel(
val fanTalkId: Long,
val writerId: Long,
val writerNickname: String,
val writerProfileImageUrl: String,
val content: String,
val createdAtText: String
)
sealed interface CreatorChannelFanTalkRightAction {
data object Report : CreatorChannelFanTalkRightAction
data class OwnerMore(
val showEdit: Boolean,
val showDelete: Boolean
) : CreatorChannelFanTalkRightAction
}

View File

@@ -0,0 +1,135 @@
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk
import android.app.Application
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkResponse
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkRightAction
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.toFanTalkUiModels
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28], application = Application::class)
class CreatorChannelFanTalkMapperTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val relativeTimeTextFormatter = AndroidUtcRelativeTimeTextFormatter(context)
@Test
fun `기본 필드와 상대 시간을 매핑한다`() {
val item = listOf(fanTalk(createdAtUtc = System.currentTimeMillis().toString()))
.toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L)
.single()
assertEquals(1L, item.fanTalkId)
assertEquals(10L, item.writerId)
assertEquals("writer", item.writerNickname)
assertEquals("profile.png", item.writerProfileImageUrl)
assertEquals(context.getString(R.string.character_comment_time_just_now), item.createdAtText)
assertEquals("hello fan talk", item.content)
}
@Test
fun `creatorReplies가 비어 있으면 reply는 null이다`() {
val item = listOf(fanTalk(creatorReplies = emptyList()))
.toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L)
.single()
assertNull(item.reply)
}
@Test
fun `creatorReplies가 여러 개이면 첫 번째 reply만 매핑한다`() {
val item = listOf(
fanTalk(
creatorReplies = listOf(
reply(fanTalkId = 2L, content = "first reply"),
reply(fanTalkId = 3L, content = "second reply")
)
)
).toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L).single()
assertEquals(2L, item.reply?.fanTalkId)
assertEquals("first reply", item.reply?.content)
}
@Test
fun `내가 쓴 글이면 수정과 삭제 owner more action이다`() {
val item = listOf(fanTalk(writerId = 10L))
.toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 10L)
.single()
val action = item.rightAction as CreatorChannelFanTalkRightAction.OwnerMore
assertTrue(action.showEdit)
assertTrue(action.showDelete)
}
@Test
fun `내 채널의 타인 글이면 삭제만 가능한 owner more action이다`() {
val item = listOf(fanTalk(writerId = 11L))
.toFanTalkUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L)
.single()
val action = item.rightAction as CreatorChannelFanTalkRightAction.OwnerMore
assertEquals(false, action.showEdit)
assertTrue(action.showDelete)
}
@Test
fun `내가 쓴 글도 아니고 내 채널도 아니면 신고 action이다`() {
val item = listOf(fanTalk(writerId = 11L))
.toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 10L)
.single()
assertTrue(item.rightAction is CreatorChannelFanTalkRightAction.Report)
}
@Test
fun `원글과 답글 content가 빈 문자열이어도 item을 유지한다`() {
val item = listOf(fanTalk(content = "", creatorReplies = listOf(reply(content = ""))))
.toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L)
.single()
assertEquals("", item.content)
assertEquals("", item.reply?.content)
}
private fun fanTalk(
fanTalkId: Long = 1L,
writerId: Long = 10L,
writerProfileImageUrl: String = "profile.png",
content: String = "hello fan talk",
createdAtUtc: String = "2026-06-21T00:00:00Z",
creatorReplies: List<CreatorChannelFanTalkReplyResponse> = emptyList()
) = CreatorChannelFanTalkResponse(
fanTalkId = fanTalkId,
writerId = writerId,
writerNickname = "writer",
writerProfileImageUrl = writerProfileImageUrl,
content = content,
createdAtUtc = createdAtUtc,
creatorReplies = creatorReplies
)
private fun reply(
fanTalkId: Long = 2L,
writerId: Long = 20L,
content: String = "reply"
) = CreatorChannelFanTalkReplyResponse(
fanTalkId = fanTalkId,
writerId = writerId,
writerNickname = "creator",
writerProfileImageUrl = "creator.png",
content = content,
createdAtUtc = "2026-06-21T00:00:00Z"
)
}