feat(creator): 채널 홈 UI 상태를 추가한다

This commit is contained in:
2026-06-13 17:19:58 +09:00
parent 0ae6596816
commit a355838039
5 changed files with 417 additions and 0 deletions

View File

@@ -0,0 +1,233 @@
package kr.co.vividnext.sodalive.v2.creator.channel
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
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelCommunityPostResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelCreatorResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelDonationResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelFanTalkResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelFanTalkSummaryResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelHomeResponse
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.data.CreatorChannelSnsResponse
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeSection
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
import kr.co.vividnext.sodalive.v2.creator.channel.model.toUiContent
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class CreatorChannelHomeMapperTest {
@Test
fun `크리에이터 정보와 탭 순서를 UI content에 그대로 매핑한다`() {
val content = response().toUiContent()
assertEquals(100L, content.header.creatorId)
assertEquals(200L, content.header.characterId)
assertEquals("소다", content.header.nickname)
assertEquals("https://example.com/profile.png", content.header.profileImageUrl)
assertEquals(1234, content.header.followerCount)
assertTrue(content.header.isFollow)
assertFalse(content.header.isNotify)
assertTrue(content.header.isAiChatAvailable)
assertTrue(content.header.isDmAvailable)
assertEquals(
listOf("", "라이브", "오디오", "시리즈", "커뮤니티", "팬Talk", "후원"),
content.tabs.map(CreatorChannelTab::label)
)
}
@Test
fun `null 단건 콘텐츠와 빈 리스트와 blank SNS는 section을 생성하지 않는다`() {
val content = response(
currentLive = null,
latestAudioContent = null,
channelDonations = emptyList(),
notices = emptyList(),
schedules = emptyList(),
audioContents = emptyList(),
series = emptyList(),
communities = emptyList(),
fanTalk = CreatorChannelFanTalkSummaryResponse(totalCount = 0, latestFanTalk = null),
introduce = "",
sns = CreatorChannelSnsResponse(
instagramUrl = " ",
fancimmUrl = "",
xUrl = "",
youtubeUrl = "",
kakaoOpenChatUrl = ""
)
).toUiContent()
assertFalse(content.sections.any { it is CreatorChannelHomeSection.CurrentLive })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.LatestAudioContent })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.Donations })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.Notices })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.Schedules })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.AudioContents })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.Series })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.Communities })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.FanTalk })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.Introduce })
assertFalse(content.sections.any { it is CreatorChannelHomeSection.Sns })
}
@Test
fun `값이 있는 홈 필드는 section으로 생성하고 blank SNS URL만 제외한다`() {
val content = response().toUiContent()
assertTrue(content.sections.any { it is CreatorChannelHomeSection.CurrentLive })
assertTrue(content.sections.any { it is CreatorChannelHomeSection.LatestAudioContent })
assertTrue(content.sections.any { it is CreatorChannelHomeSection.Donations })
assertTrue(content.sections.any { it is CreatorChannelHomeSection.Notices })
assertTrue(content.sections.any { it is CreatorChannelHomeSection.Schedules })
assertTrue(content.sections.any { it is CreatorChannelHomeSection.AudioContents })
assertTrue(content.sections.any { it is CreatorChannelHomeSection.Series })
assertTrue(content.sections.any { it is CreatorChannelHomeSection.Communities })
assertTrue(content.sections.any { it is CreatorChannelHomeSection.FanTalk })
assertTrue(content.sections.any { it is CreatorChannelHomeSection.Introduce })
val sns = content.sections.filterIsInstance<CreatorChannelHomeSection.Sns>().single()
assertEquals(listOf("Instagram", "YouTube"), sns.items.map { it.label })
assertEquals(listOf("https://instagram.example", "https://youtube.example"), sns.items.map { it.url })
}
private fun response(
currentLive: CreatorChannelLiveResponse? = live(),
latestAudioContent: CreatorChannelAudioContentResponse? = audioContent(10L),
channelDonations: List<CreatorChannelDonationResponse> = listOf(donation()),
notices: List<CreatorChannelCommunityPostResponse> = listOf(post(20L)),
schedules: List<CreatorChannelScheduleResponse> = listOf(schedule()),
audioContents: List<CreatorChannelAudioContentResponse> = listOf(audioContent(11L)),
series: List<CreatorChannelSeriesResponse> = listOf(series()),
communities: List<CreatorChannelCommunityPostResponse> = listOf(post(21L)),
fanTalk: CreatorChannelFanTalkSummaryResponse = fanTalk(),
introduce: String = "소개입니다",
activity: CreatorChannelActivityResponse = activity(),
sns: CreatorChannelSnsResponse = CreatorChannelSnsResponse(
instagramUrl = "https://instagram.example",
fancimmUrl = "",
xUrl = " ",
youtubeUrl = "https://youtube.example",
kakaoOpenChatUrl = ""
)
) = CreatorChannelHomeResponse(
creator = CreatorChannelCreatorResponse(
creatorId = 100L,
characterId = 200L,
nickname = "소다",
profileImageUrl = "https://example.com/profile.png",
followerCount = 1234,
isAiChatAvailable = true,
isDmAvailable = true,
isFollow = true,
isNotify = false
),
currentLive = currentLive,
latestAudioContent = latestAudioContent,
channelDonations = channelDonations,
notices = notices,
schedules = schedules,
audioContents = audioContents,
series = series,
communities = communities,
fanTalk = fanTalk,
introduce = introduce,
activity = activity,
sns = sns
)
private fun live() = CreatorChannelLiveResponse(
liveId = 1L,
title = "라이브",
coverImageUrl = "https://example.com/live.png",
beginDateTimeUtc = "2026-06-11T12:00:00Z",
price = 10,
isAdult = false
)
private fun audioContent(id: Long) = CreatorChannelAudioContentResponse(
audioContentId = id,
title = "오디오 $id",
duration = "10:00",
imageUrl = "https://example.com/audio.png",
price = 10,
isPointAvailable = true,
isFirstContent = false,
seriesName = "시리즈",
isOriginalSeries = true
)
private fun donation() = CreatorChannelDonationResponse(
donationId = 1L,
memberId = 2L,
nickname = "후원자",
profileImageUrl = "https://example.com/member.png",
can = 100,
isSecret = false,
message = "응원합니다",
createdAtUtc = "2026-06-11T12:00:00Z"
)
private fun schedule() = CreatorChannelScheduleResponse(
scheduledAtUtc = "2026-06-12T12:00:00Z",
title = "일정",
type = CreatorActivityType.Live,
targetId = 1L
)
private fun series() = CreatorChannelSeriesResponse(
seriesId = 1L,
title = "시리즈",
coverImageUrl = "https://example.com/series.png",
publishedDaysOfWeek = "MON",
isComplete = false,
numberOfContent = 3,
isNew = true,
isPopular = false,
isOriginal = true
)
private fun post(id: Long) = CreatorChannelCommunityPostResponse(
postId = id,
creatorId = 100L,
creatorNickname = "소다",
creatorProfileUrl = "https://example.com/profile.png",
imageUrl = null,
audioUrl = null,
content = "게시글",
price = 0,
dateUtc = "2026-06-11T12:00:00Z",
existOrdered = false,
likeCount = 1,
commentCount = 2
)
private fun fanTalk() = CreatorChannelFanTalkSummaryResponse(
totalCount = 1,
latestFanTalk = CreatorChannelFanTalkResponse(
fanTalkId = 1L,
memberId = 2L,
nickname = "",
profileImageUrl = "https://example.com/fan.png",
content = "팬톡",
languageCode = "ko",
createdAtUtc = "2026-06-11T12:00:00Z"
)
)
private fun activity() = CreatorChannelActivityResponse(
debutDateUtc = "2026-01-01T00:00:00Z",
dDay = "D+1",
liveCount = 1,
liveDurationHours = 2,
liveContributorCount = 3,
audioContentCount = 4,
seriesCount = 5
)
}

View File

@@ -0,0 +1,46 @@
package kr.co.vividnext.sodalive.v2.creator.channel
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarState
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
class CreatorChannelTitleBarStateTest {
@Test
fun `팔로우하지 않은 상태는 follow 아이콘만 표시한다`() {
val state = CreatorChannelTitleBarState.from(isFollow = false, isNotify = false, isInProgress = false)
assertEquals(R.drawable.ic_new_follow, state.followIconResId)
assertNull(state.bellIconResId)
assertTrue(state.isActionEnabled)
}
@Test
fun `팔로우와 알림이 모두 켜진 상태는 following과 colored bell 아이콘을 표시한다`() {
val state = CreatorChannelTitleBarState.from(isFollow = true, isNotify = true, isInProgress = false)
assertEquals(R.drawable.ic_new_following, state.followIconResId)
assertEquals(R.drawable.ic_bar_bell_colored, state.bellIconResId)
assertTrue(state.isActionEnabled)
}
@Test
fun `팔로우만 켜진 상태는 following과 기본 bell 아이콘을 표시한다`() {
val state = CreatorChannelTitleBarState.from(isFollow = true, isNotify = false, isInProgress = false)
assertEquals(R.drawable.ic_new_following, state.followIconResId)
assertEquals(R.drawable.ic_bar_bell, state.bellIconResId)
assertTrue(state.isActionEnabled)
}
@Test
fun `요청 진행 중에는 액션을 비활성화한다`() {
val state = CreatorChannelTitleBarState.from(isFollow = true, isNotify = true, isInProgress = true)
assertFalse(state.isActionEnabled)
}
}