From a20655badb7fd914c624fe0f21ee0ad595e93d42 Mon Sep 17 00:00:00 2001 From: klaus Date: Mon, 15 Jun 2026 13:21:28 +0900 Subject: [PATCH] =?UTF-8?q?test(creator):=20=EC=B1=84=EB=84=90=20=ED=99=88?= =?UTF-8?q?=20=EB=A7=A4=ED=8D=BC=20=EA=B2=80=EC=A6=9D=EC=9D=84=20=EB=B3=B4?= =?UTF-8?q?=EA=B0=95=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/CreatorChannelHomeMappers.kt | 32 ++++++--- app/src/main/res/values/colors.xml | 1 + .../channel/CreatorChannelHomeMapperTest.kt | 68 +++++++++++++++++-- 3 files changed, 86 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeMappers.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeMappers.kt index f6f4c6c0..5f4eb450 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeMappers.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeMappers.kt @@ -1,7 +1,10 @@ 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.CreatorChannelSnsResponse +import java.net.URI fun CreatorChannelHomeResponse.toUiContent(): CreatorChannelHomeUiState.Content { val sections = buildList { @@ -9,7 +12,9 @@ fun CreatorChannelHomeResponse.toUiContent(): CreatorChannelHomeUiState.Content latestAudioContent?.let { add(CreatorChannelHomeSection.LatestAudioContent(it)) } channelDonations.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Donations(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)) } series.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Series(it)) } communities.takeIf { it.isNotEmpty() }?.let { add(CreatorChannelHomeSection.Communities(it)) } @@ -37,13 +42,24 @@ fun CreatorChannelHomeResponse.toUiContent(): CreatorChannelHomeUiState.Content } private fun CreatorChannelSnsResponse.toUiItems(): List = buildList { - instagramUrl.toSnsItem("Instagram")?.let(::add) - fancimmUrl.toSnsItem("Fancimm")?.let(::add) - xUrl.toSnsItem("X")?.let(::add) - youtubeUrl.toSnsItem("YouTube")?.let(::add) - kakaoOpenChatUrl.toSnsItem("Kakao Open Chat")?.let(::add) + instagramUrl.toSnsItem(R.drawable.ic_sns_instagram, "Instagram")?.let(::add) + youtubeUrl.toSnsItem(R.drawable.ic_sns_youtube, "YouTube")?.let(::add) + xUrl.toSnsItem(R.drawable.ic_sns_x, "X")?.let(::add) + kakaoOpenChatUrl.toSnsItem(R.drawable.ic_sns_kakao, "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 { - CreatorChannelSnsUiItem(label = label, url = it) +private fun String.toSnsItem( + @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 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 7197565f..edc45233 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -173,4 +173,5 @@ #494949 #343434 #202020 + #00EAFF diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeMapperTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeMapperTest.kt index db261e4b..b2dd0f74 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeMapperTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeMapperTest.kt @@ -1,5 +1,6 @@ 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.creator.channel.data.CreatorChannelActivityResponse import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse @@ -37,8 +38,16 @@ class CreatorChannelHomeMapperTest { assertTrue(content.header.isAiChatAvailable) assertTrue(content.header.isDmAvailable) assertEquals( - listOf("홈", "라이브", "오디오", "시리즈", "커뮤니티", "팬Talk", "후원"), - content.tabs.map(CreatorChannelTab::label) + listOf( + 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 }) } + @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().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().single() + + assertEquals(listOf(1L, 2L, 3L), schedules.schedules.map { it.targetId }) + } + private fun response( currentLive: CreatorChannelLiveResponse? = live(), latestAudioContent: CreatorChannelAudioContentResponse? = audioContent(10L), @@ -174,11 +224,15 @@ class CreatorChannelHomeMapperTest { createdAtUtc = "2026-06-11T12:00:00Z" ) - private fun schedule() = CreatorChannelScheduleResponse( - scheduledAtUtc = "2026-06-12T12:00:00Z", - title = "일정", - type = CreatorActivityType.Live, - targetId = 1L + private fun schedule( + id: Long = 1L, + scheduledAtUtc: String = "2026-06-12T12:00:00Z", + type: CreatorActivityType = CreatorActivityType.Live + ) = CreatorChannelScheduleResponse( + scheduledAtUtc = scheduledAtUtc, + title = "일정 $id", + type = type, + targetId = id ) private fun series() = CreatorChannelSeriesResponse(