test(creator): 채널 홈 매퍼 검증을 보강한다
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user