test(creator): 채널 홈 매퍼 검증을 보강한다

This commit is contained in:
2026-06-15 13:21:28 +09:00
parent 11fd892310
commit a20655badb
3 changed files with 86 additions and 15 deletions

View File

@@ -1,7 +1,10 @@
package kr.co.vividnext.sodalive.v2.creator.channel.model package kr.co.vividnext.sodalive.v2.creator.channel.model
import androidx.annotation.DrawableRes
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelHomeResponse import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelHomeResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSnsResponse import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSnsResponse
import java.net.URI
fun CreatorChannelHomeResponse.toUiContent(): CreatorChannelHomeUiState.Content { fun CreatorChannelHomeResponse.toUiContent(): CreatorChannelHomeUiState.Content {
val sections = buildList { val sections = buildList {
@@ -9,7 +12,9 @@ fun CreatorChannelHomeResponse.toUiContent(): CreatorChannelHomeUiState.Content
latestAudioContent?.let { add(CreatorChannelHomeSection.LatestAudioContent(it)) } latestAudioContent?.let { add(CreatorChannelHomeSection.LatestAudioContent(it)) }
channelDonations.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Donations(it)) } channelDonations.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Donations(it)) }
notices.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Notices(it)) } notices.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Notices(it)) }
schedules.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Schedules(it)) } schedules.sortedBy { it.scheduledAtUtc }.take(MAX_SCHEDULE_ITEM_COUNT)
.takeIf { it.isNotEmpty() }
?.let { add(CreatorChannelHomeSection.Schedules(it)) }
audioContents.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.AudioContents(it)) } audioContents.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.AudioContents(it)) }
series.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Series(it)) } series.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Series(it)) }
communities.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Communities(it)) } communities.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Communities(it)) }
@@ -37,13 +42,24 @@ fun CreatorChannelHomeResponse.toUiContent(): CreatorChannelHomeUiState.Content
} }
private fun CreatorChannelSnsResponse.toUiItems(): List<CreatorChannelSnsUiItem> = buildList { private fun CreatorChannelSnsResponse.toUiItems(): List<CreatorChannelSnsUiItem> = buildList {
instagramUrl.toSnsItem("Instagram")?.let(::add) instagramUrl.toSnsItem(R.drawable.ic_sns_instagram, "Instagram")?.let(::add)
fancimmUrl.toSnsItem("Fancimm")?.let(::add) youtubeUrl.toSnsItem(R.drawable.ic_sns_youtube, "YouTube")?.let(::add)
xUrl.toSnsItem("X")?.let(::add) xUrl.toSnsItem(R.drawable.ic_sns_x, "X")?.let(::add)
youtubeUrl.toSnsItem("YouTube")?.let(::add) kakaoOpenChatUrl.toSnsItem(R.drawable.ic_sns_kakao, "Kakao Open Chat")?.let(::add)
kakaoOpenChatUrl.toSnsItem("Kakao Open Chat")?.let(::add) fancimmUrl.toSnsItem(R.drawable.ic_sns_fancimm, "Fancimm")?.let(::add)
} }
private fun String.toSnsItem(label: String): CreatorChannelSnsUiItem? = takeIf { it.isNotBlank() }?.let { private fun String.toSnsItem(
CreatorChannelSnsUiItem(label = label, url = it) @DrawableRes iconResId: Int,
label: String
): CreatorChannelSnsUiItem? = trim().takeIf(::isValidCreatorChannelSnsUrl)?.let {
CreatorChannelSnsUiItem(iconResId = iconResId, label = label, url = it)
} }
internal fun isValidCreatorChannelSnsUrl(url: String): Boolean {
val uri = runCatching { URI(url) }.getOrNull() ?: return false
val scheme = uri.scheme?.lowercase() ?: return false
return scheme in setOf("http", "https") && !uri.host.isNullOrBlank()
}
private const val MAX_SCHEDULE_ITEM_COUNT = 3

View File

@@ -173,4 +173,5 @@
<color name="gray_700">#494949</color> <color name="gray_700">#494949</color>
<color name="gray_800">#343434</color> <color name="gray_800">#343434</color>
<color name="gray_900">#202020</color> <color name="gray_900">#202020</color>
<color name="creator_channel_donation_cyan">#00EAFF</color>
</resources> </resources>

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.creator.channel package kr.co.vividnext.sodalive.v2.creator.channel
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelActivityResponse import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelActivityResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
@@ -37,8 +38,16 @@ class CreatorChannelHomeMapperTest {
assertTrue(content.header.isAiChatAvailable) assertTrue(content.header.isAiChatAvailable)
assertTrue(content.header.isDmAvailable) assertTrue(content.header.isDmAvailable)
assertEquals( assertEquals(
listOf("", "라이브", "오디오", "시리즈", "커뮤니티", "팬Talk", "후원"), listOf(
content.tabs.map(CreatorChannelTab::label) R.string.creator_channel_tab_home,
R.string.creator_channel_tab_live,
R.string.creator_channel_tab_audio,
R.string.creator_channel_tab_series,
R.string.creator_channel_tab_community,
R.string.creator_channel_tab_fantalk,
R.string.creator_channel_tab_donation
),
content.tabs.map(CreatorChannelTab::labelResId)
) )
} }
@@ -97,6 +106,47 @@ class CreatorChannelHomeMapperTest {
assertEquals(listOf("https://instagram.example", "https://youtube.example"), sns.items.map { it.url }) assertEquals(listOf("https://instagram.example", "https://youtube.example"), sns.items.map { it.url })
} }
@Test
fun `SNS는 Figma 순서로 매핑하고 유효하지 않은 URL은 제외한다`() {
val content = response(
sns = CreatorChannelSnsResponse(
instagramUrl = "https://instagram.example",
fancimmUrl = "https://fancimm.example",
xUrl = "not-a-url",
youtubeUrl = "https://youtube.example",
kakaoOpenChatUrl = "kakao-open-chat"
)
).toUiContent()
val sns = content.sections.filterIsInstance<CreatorChannelHomeSection.Sns>().single()
assertEquals(listOf("Instagram", "YouTube", "Fancimm"), sns.items.map { it.label })
assertEquals(
listOf("https://instagram.example", "https://youtube.example", "https://fancimm.example"),
sns.items.map { it.url }
)
assertEquals(
listOf(R.drawable.ic_sns_instagram, R.drawable.ic_sns_youtube, R.drawable.ic_sns_fancimm),
sns.items.map { it.iconResId }
)
}
@Test
fun `스케줄은 scheduledAtUtc가 가까운 순으로 최대 3개만 매핑한다`() {
val content = response(
schedules = listOf(
schedule(id = 4L, scheduledAtUtc = "2026-06-12T13:00:00Z"),
schedule(id = 2L, scheduledAtUtc = "2026-06-12T11:00:00Z"),
schedule(id = 1L, scheduledAtUtc = "2026-06-12T10:00:00Z"),
schedule(id = 3L, scheduledAtUtc = "2026-06-12T12:00:00Z")
)
).toUiContent()
val schedules = content.sections.filterIsInstance<CreatorChannelHomeSection.Schedules>().single()
assertEquals(listOf(1L, 2L, 3L), schedules.schedules.map { it.targetId })
}
private fun response( private fun response(
currentLive: CreatorChannelLiveResponse? = live(), currentLive: CreatorChannelLiveResponse? = live(),
latestAudioContent: CreatorChannelAudioContentResponse? = audioContent(10L), latestAudioContent: CreatorChannelAudioContentResponse? = audioContent(10L),
@@ -174,11 +224,15 @@ class CreatorChannelHomeMapperTest {
createdAtUtc = "2026-06-11T12:00:00Z" createdAtUtc = "2026-06-11T12:00:00Z"
) )
private fun schedule() = CreatorChannelScheduleResponse( private fun schedule(
scheduledAtUtc = "2026-06-12T12:00:00Z", id: Long = 1L,
title = "일정", scheduledAtUtc: String = "2026-06-12T12:00:00Z",
type = CreatorActivityType.Live, type: CreatorActivityType = CreatorActivityType.Live
targetId = 1L ) = CreatorChannelScheduleResponse(
scheduledAtUtc = scheduledAtUtc,
title = "일정 $id",
type = type,
targetId = id
) )
private fun series() = CreatorChannelSeriesResponse( private fun series() = CreatorChannelSeriesResponse(