feat(widget): 크리에이터 랭킹 위젯을 추가한다

This commit is contained in:
2026-05-20 10:41:07 +09:00
parent 6fda122091
commit 01fea58e4c
35 changed files with 2341 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
package kr.co.vividnext.sodalive.v2.widget.creatorranking
import org.junit.Assert.assertEquals
import org.junit.Test
class CreatorRankingAdapterLayoutTest {
@Test
fun `span count supports full two and three column ranking rows`() {
assertEquals(6, CreatorRankingAdapter.GRID_SPAN_COUNT)
}
@Test
fun `span lookup maps rank groups to expected row widths`() {
val spanLookup = CreatorRankingAdapter.createSpanSizeLookup()
assertEquals(6, spanLookup.getSpanSize(0))
(1..6).forEach { position ->
assertEquals(3, spanLookup.getSpanSize(position))
}
(7..9).forEach { position ->
assertEquals(2, spanLookup.getSpanSize(position))
}
listOf(10, 11, 20).forEach { position ->
assertEquals(6, spanLookup.getSpanSize(position))
}
}
}

View File

@@ -0,0 +1,24 @@
package kr.co.vividnext.sodalive.v2.widget.creatorranking
import android.os.Build
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class CreatorRankingBlurTest {
@Test
fun `blocked image uses coil blur fallback before Android 12`() {
assertTrue(CreatorRankingBlur.shouldUseCoilBlur(enabled = true, sdkInt = Build.VERSION_CODES.R))
}
@Test
fun `blocked image keeps render effect path on Android 12 or later`() {
assertFalse(CreatorRankingBlur.shouldUseCoilBlur(enabled = true, sdkInt = Build.VERSION_CODES.S))
}
@Test
fun `accessible image does not use coil blur fallback`() {
assertFalse(CreatorRankingBlur.shouldUseCoilBlur(enabled = false, sdkInt = Build.VERSION_CODES.R))
}
}

View File

@@ -0,0 +1,53 @@
package kr.co.vividnext.sodalive.v2.widget.creatorranking
import kr.co.vividnext.sodalive.R
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 CreatorRankingDeltaPresentationTest {
@Test
fun `increase shows caret and amount`() {
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.Increase, amount = 4)
assertEquals(R.drawable.ic_rank_caret_increase, presentation.iconRes)
assertTrue(presentation.showAmount)
assertEquals("4", presentation.amountText)
assertTrue(presentation.showPillBackground)
assertEquals(14, presentation.iconWidthDp)
assertEquals(14, presentation.iconHeightDp)
}
@Test
fun `decrease shows caret and amount`() {
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.Decrease, amount = 4)
assertEquals(R.drawable.ic_rank_caret_decrease, presentation.iconRes)
assertTrue(presentation.showAmount)
assertEquals("4", presentation.amountText)
}
@Test
fun `stay shows stay icon without amount`() {
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.Stay, amount = 0)
assertEquals(R.drawable.ic_rank_caret_stay, presentation.iconRes)
assertFalse(presentation.showAmount)
assertNull(presentation.amountText)
}
@Test
fun `new shows new image without amount`() {
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.New, amount = 0)
assertEquals(R.drawable.ic_rank_new, presentation.iconRes)
assertFalse(presentation.showAmount)
assertNull(presentation.amountText)
assertFalse(presentation.showPillBackground)
assertEquals(36, presentation.iconWidthDp)
assertEquals(23, presentation.iconHeightDp)
}
}

View File

@@ -0,0 +1,66 @@
package kr.co.vividnext.sodalive.v2.widget.creatorranking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class CreatorRankingItemTest {
@Test
fun `blocked item is inaccessible`() {
val item = sampleItem(isBlocked = true)
assertTrue(item.isInaccessible)
assertFalse(item.isTouchable)
}
@Test
fun `accessible item is touchable`() {
val item = sampleItem()
assertFalse(item.isInaccessible)
assertTrue(item.isTouchable)
}
@Test
fun `top ten blocked item hides creator name`() {
val item = sampleItem(rank = 10, isBlocked = true)
assertEquals("", item.displayName(inaccessibleMessage = "접근할 수 없는 정보입니다."))
}
@Test
fun `rank 11 blocked item shows inaccessible message`() {
val item = sampleItem(rank = 11, isBlocked = true)
assertEquals("접근할 수 없는 정보입니다.", item.displayName(inaccessibleMessage = "접근할 수 없는 정보입니다."))
}
@Test
fun `accessible item shows creator name`() {
val item = sampleItem(creatorName = "크리에이터 이름")
assertEquals("크리에이터 이름", item.displayName(inaccessibleMessage = "접근할 수 없는 정보입니다."))
}
private fun sampleItem(
creatorId: Long = 1L,
rank: Int = 1,
previousRank: Int? = 5,
rankChangeType: CreatorRankingChangeType = CreatorRankingChangeType.Increase,
rankChangeAmount: Int = 4,
creatorName: String = "크리에이터 이름",
imageUrl: String = "https://example.com/image.png",
isBlocked: Boolean = false
) = CreatorRankingItem(
creatorId = creatorId,
rank = rank,
previousRank = previousRank,
rankChangeType = rankChangeType,
rankChangeAmount = rankChangeAmount,
creatorName = creatorName,
imageUrl = imageUrl,
isBlocked = isBlocked
)
}

View File

@@ -0,0 +1,68 @@
package kr.co.vividnext.sodalive.v2.widget.creatorranking
import org.junit.Assert.assertEquals
import org.junit.Test
class CreatorRankingLayoutCalculatorTest {
@Test
fun `large card fills available width as square`() {
val size = CreatorRankingLayoutCalculator.calculate(
parentWidthPx = 374,
horizontalGapPx = 4,
placement = CreatorRankingPlacement(CreatorRankingCardVariant.Large, itemsPerRow = 1)
)
assertEquals(374, size.widthPx)
assertEquals(374, size.heightPx)
}
@Test
fun `large card excludes parent horizontal padding from available width`() {
val size = CreatorRankingLayoutCalculator.calculate(
parentWidthPx = 374,
parentHorizontalPaddingPx = 40,
horizontalGapPx = 4,
placement = CreatorRankingPlacement(CreatorRankingCardVariant.Large, itemsPerRow = 1)
)
assertEquals(334, size.widthPx)
assertEquals(334, size.heightPx)
}
@Test
fun `compact card can use two columns`() {
val size = CreatorRankingLayoutCalculator.calculate(
parentWidthPx = 374,
horizontalGapPx = 4,
placement = CreatorRankingPlacement(CreatorRankingCardVariant.Compact, itemsPerRow = 2)
)
assertEquals(185, size.widthPx)
assertEquals(185, size.heightPx)
}
@Test
fun `compact card can use three columns`() {
val size = CreatorRankingLayoutCalculator.calculate(
parentWidthPx = 374,
horizontalGapPx = 4,
placement = CreatorRankingPlacement(CreatorRankingCardVariant.Compact, itemsPerRow = 3)
)
assertEquals(122, size.widthPx)
assertEquals(122, size.heightPx)
}
@Test
fun `horizontal card keeps figma aspect ratio`() {
val size = CreatorRankingLayoutCalculator.calculate(
parentWidthPx = 374,
horizontalGapPx = 4,
placement = CreatorRankingPlacement(CreatorRankingCardVariant.Horizontal, itemsPerRow = 1)
)
assertEquals(374, size.widthPx)
assertEquals(100, size.heightPx)
}
}

View File

@@ -0,0 +1,50 @@
package kr.co.vividnext.sodalive.v2.widget.creatorranking
import org.junit.Assert.assertEquals
import org.junit.Test
class CreatorRankingPlacementTest {
@Test
fun `rank 1 uses large variant and one item row`() {
val placement = CreatorRankingPlacement.fromRank(1)
assertEquals(CreatorRankingCardVariant.Large, placement.variant)
assertEquals(1, placement.itemsPerRow)
}
@Test
fun `rank 2 to 7 uses compact variant and two item row`() {
(2..7).forEach { rank ->
val placement = CreatorRankingPlacement.fromRank(rank)
assertEquals(CreatorRankingCardVariant.Compact, placement.variant)
assertEquals(2, placement.itemsPerRow)
}
}
@Test
fun `rank 8 to 10 uses compact variant and three item row`() {
(8..10).forEach { rank ->
val placement = CreatorRankingPlacement.fromRank(rank)
assertEquals(CreatorRankingCardVariant.Compact, placement.variant)
assertEquals(3, placement.itemsPerRow)
}
}
@Test
fun `rank 11 or greater uses horizontal variant and one item row`() {
listOf(11, 12, 100).forEach { rank ->
val placement = CreatorRankingPlacement.fromRank(rank)
assertEquals(CreatorRankingCardVariant.Horizontal, placement.variant)
assertEquals(1, placement.itemsPerRow)
}
}
@Test(expected = IllegalArgumentException::class)
fun `rank less than 1 is invalid`() {
CreatorRankingPlacement.fromRank(0)
}
}