feat(widget): 콘텐츠 랭킹 위젯을 추가한다

This commit is contained in:
2026-05-20 12:00:23 +09:00
parent 01fea58e4c
commit 36ffbc6cdb
35 changed files with 2365 additions and 39 deletions

View File

@@ -0,0 +1,51 @@
package kr.co.vividnext.sodalive.v2.widget.contentranking
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingChangeType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class ContentRankingDeltaPresentationTest {
@Test
fun `increase shows amount and increase caret`() {
val presentation = ContentRankingDeltaPresentation.from(RankingChangeType.Increase, amount = 4)
assertEquals(R.drawable.ic_rank_caret_increase, presentation.iconRes)
assertEquals("4", presentation.amountText)
assertTrue(presentation.showAmount)
assertFalse(presentation.replaceWithNewIcon)
}
@Test
fun `decrease shows amount and decrease caret`() {
val presentation = ContentRankingDeltaPresentation.from(RankingChangeType.Decrease, amount = 2)
assertEquals(R.drawable.ic_rank_caret_decrease, presentation.iconRes)
assertEquals("2", presentation.amountText)
assertTrue(presentation.showAmount)
assertFalse(presentation.replaceWithNewIcon)
}
@Test
fun `stay shows only stay icon`() {
val presentation = ContentRankingDeltaPresentation.from(RankingChangeType.Stay, amount = 0)
assertEquals(R.drawable.ic_rank_caret_stay, presentation.iconRes)
assertEquals("", presentation.amountText)
assertFalse(presentation.showAmount)
assertFalse(presentation.replaceWithNewIcon)
}
@Test
fun `new replaces rank num with new icon`() {
val presentation = ContentRankingDeltaPresentation.from(RankingChangeType.New, amount = null)
assertEquals(R.drawable.ic_rank_new, presentation.iconRes)
assertEquals("", presentation.amountText)
assertFalse(presentation.showAmount)
assertTrue(presentation.replaceWithNewIcon)
}
}

View File

@@ -0,0 +1,92 @@
package kr.co.vividnext.sodalive.v2.widget.contentranking
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingChangeType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class ContentRankingItemTest {
@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 content and creator names`() {
val item = sampleItem(rank = 10, isBlocked = true)
assertEquals("", item.displayContentName(inaccessibleMessage = "접근할 수 없는 정보입니다."))
assertEquals("", item.displayCreatorName())
}
@Test
fun `rank 11 blocked item shows inaccessible message as single line`() {
val item = sampleItem(rank = 11, isBlocked = true)
assertEquals("접근할 수 없는 정보입니다.", item.displayContentName(inaccessibleMessage = "접근할 수 없는 정보입니다."))
assertEquals("", item.displayCreatorName())
}
@Test
fun `accessible item shows original names`() {
val item = sampleItem(contentName = "콘텐츠 이름", creatorName = "크리에이터 이름")
assertEquals("콘텐츠 이름", item.displayContentName(inaccessibleMessage = "접근할 수 없는 정보입니다."))
assertEquals("크리에이터 이름", item.displayCreatorName())
}
@Test
fun `content title max length follows rank range`() {
assertEquals(16, sampleItem(rank = 1).contentNameMaxLength)
assertEquals(8, sampleItem(rank = 2).contentNameMaxLength)
assertEquals(8, sampleItem(rank = 10).contentNameMaxLength)
assertEquals(12, sampleItem(rank = 11).contentNameMaxLength)
}
@Test
fun `accessible content name is truncated by rank range`() {
assertEquals(
"1234567890123456...",
sampleItem(rank = 1, contentName = "12345678901234567").displayContentName(inaccessibleMessage = "접근할 수 없는 정보입니다.")
)
assertEquals(
"12345678...",
sampleItem(rank = 2, contentName = "123456789").displayContentName(inaccessibleMessage = "접근할 수 없는 정보입니다.")
)
assertEquals(
"123456789012...",
sampleItem(rank = 11, contentName = "1234567890123").displayContentName(inaccessibleMessage = "접근할 수 없는 정보입니다.")
)
}
private fun sampleItem(
rank: Int = 1,
contentName: String = "콘텐츠 이름",
creatorName: String = "크리에이터 이름",
isBlocked: Boolean = false
) = ContentRankingItem(
contentId = "content-1",
creatorId = "creator-1",
rank = rank,
previousRank = 5,
rankChangeType = RankingChangeType.Increase,
rankChangeAmount = 4,
contentName = contentName,
creatorName = creatorName,
imageUrl = "https://example.com/image.png",
isBlocked = isBlocked
)
}

View File

@@ -0,0 +1,55 @@
package kr.co.vividnext.sodalive.v2.widget.contentranking
import org.junit.Assert.assertEquals
import org.junit.Test
class ContentRankingLayoutCalculatorTest {
@Test
fun `large item keeps figma large ratio`() {
val size = ContentRankingLayoutCalculator.calculate(
parentWidthPx = 374,
horizontalGapPx = 4,
placement = ContentRankingPlacement(ContentRankingCardVariant.Large, itemsPerRow = 1)
)
assertEquals(374, size.widthPx)
assertEquals(238, size.heightPx)
}
@Test
fun `medium grid item width divides available width by items per row`() {
val size = ContentRankingLayoutCalculator.calculate(
parentWidthPx = 374,
horizontalGapPx = 4,
placement = ContentRankingPlacement(ContentRankingCardVariant.MediumGrid, itemsPerRow = 2)
)
assertEquals(185, size.widthPx)
assertEquals(185, size.heightPx)
}
@Test
fun `small grid item subtracts two gaps`() {
val size = ContentRankingLayoutCalculator.calculate(
parentWidthPx = 374,
horizontalGapPx = 4,
placement = ContentRankingPlacement(ContentRankingCardVariant.SmallGrid, itemsPerRow = 3)
)
assertEquals(122, size.widthPx)
assertEquals(122, size.heightPx)
}
@Test
fun `horizontal item keeps figma ratio`() {
val size = ContentRankingLayoutCalculator.calculate(
parentWidthPx = 374,
horizontalGapPx = 4,
placement = ContentRankingPlacement(ContentRankingCardVariant.Horizontal, itemsPerRow = 1)
)
assertEquals(374, size.widthPx)
assertEquals(100, size.heightPx)
}
}

View File

@@ -0,0 +1,50 @@
package kr.co.vividnext.sodalive.v2.widget.contentranking
import org.junit.Assert.assertEquals
import org.junit.Test
class ContentRankingPlacementTest {
@Test
fun `rank 1 uses large variant and one item row`() {
val placement = ContentRankingPlacement.fromRank(1)
assertEquals(ContentRankingCardVariant.Large, placement.variant)
assertEquals(1, placement.itemsPerRow)
}
@Test
fun `rank 2 to 7 uses medium grid variant and two item row`() {
(2..7).forEach { rank ->
val placement = ContentRankingPlacement.fromRank(rank)
assertEquals(ContentRankingCardVariant.MediumGrid, placement.variant)
assertEquals(2, placement.itemsPerRow)
}
}
@Test
fun `rank 8 to 10 uses small grid variant and three item row`() {
(8..10).forEach { rank ->
val placement = ContentRankingPlacement.fromRank(rank)
assertEquals(ContentRankingCardVariant.SmallGrid, 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 = ContentRankingPlacement.fromRank(rank)
assertEquals(ContentRankingCardVariant.Horizontal, placement.variant)
assertEquals(1, placement.itemsPerRow)
}
}
@Test(expected = IllegalArgumentException::class)
fun `rank less than 1 is invalid`() {
ContentRankingPlacement.fromRank(0)
}
}

View File

@@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.v2.widget.creatorranking
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingChangeType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -11,7 +12,7 @@ class CreatorRankingDeltaPresentationTest {
@Test
fun `increase shows caret and amount`() {
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.Increase, amount = 4)
val presentation = CreatorRankingDeltaPresentation.from(RankingChangeType.Increase, amount = 4)
assertEquals(R.drawable.ic_rank_caret_increase, presentation.iconRes)
assertTrue(presentation.showAmount)
@@ -23,7 +24,7 @@ class CreatorRankingDeltaPresentationTest {
@Test
fun `decrease shows caret and amount`() {
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.Decrease, amount = 4)
val presentation = CreatorRankingDeltaPresentation.from(RankingChangeType.Decrease, amount = 4)
assertEquals(R.drawable.ic_rank_caret_decrease, presentation.iconRes)
assertTrue(presentation.showAmount)
@@ -32,7 +33,7 @@ class CreatorRankingDeltaPresentationTest {
@Test
fun `stay shows stay icon without amount`() {
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.Stay, amount = 0)
val presentation = CreatorRankingDeltaPresentation.from(RankingChangeType.Stay, amount = 0)
assertEquals(R.drawable.ic_rank_caret_stay, presentation.iconRes)
assertFalse(presentation.showAmount)
@@ -41,7 +42,7 @@ class CreatorRankingDeltaPresentationTest {
@Test
fun `new shows new image without amount`() {
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.New, amount = 0)
val presentation = CreatorRankingDeltaPresentation.from(RankingChangeType.New, amount = 0)
assertEquals(R.drawable.ic_rank_new, presentation.iconRes)
assertFalse(presentation.showAmount)

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.widget.creatorranking
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingChangeType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -48,7 +49,7 @@ class CreatorRankingItemTest {
creatorId: Long = 1L,
rank: Int = 1,
previousRank: Int? = 5,
rankChangeType: CreatorRankingChangeType = CreatorRankingChangeType.Increase,
rankChangeType: RankingChangeType = RankingChangeType.Increase,
rankChangeAmount: Int = 4,
creatorName: String = "크리에이터 이름",
imageUrl: String = "https://example.com/image.png",