feat(home): HomePriorityEngine priority logic, HomeViewModel wiring, unit test coverage
This commit is contained in:
parent
2a5c40508e
commit
941f22cdbd
|
|
@ -44,6 +44,8 @@ object HomePriorityEngine {
|
|||
val gameWaiting: Boolean = false,
|
||||
val challengeWaiting: Boolean = false,
|
||||
val dailyQuestionUnanswered: Boolean = false,
|
||||
val dailyQuestionAwaitingPartner: Boolean = false,
|
||||
val dailyQuestionRevealed: Boolean = false,
|
||||
val weeklyRecapReady: Boolean = false,
|
||||
val capsuleUnlocked: Boolean = false,
|
||||
val dateReminder: Boolean = false,
|
||||
|
|
@ -60,9 +62,14 @@ object HomePriorityEngine {
|
|||
GAME_WAITING,
|
||||
DAILY_QUESTION_UNANSWERED,
|
||||
CHALLENGE_WAITING,
|
||||
// You answered; waiting on your partner. Ritual band: below actionable games/challenges,
|
||||
// above reflective/generic content. Becomes the hero only when nothing more urgent is active.
|
||||
DAILY_QUESTION_AWAITING_PARTNER,
|
||||
WEEKLY_RECAP_READY,
|
||||
CAPSULE_UNLOCKED,
|
||||
DATE_REMINDER,
|
||||
// You already revealed today's question — a low-priority "keep the conversation going" closure card.
|
||||
DAILY_QUESTION_REVEALED,
|
||||
SUGGESTED_PACK,
|
||||
EXPLORE_GAMES
|
||||
}
|
||||
|
|
@ -129,6 +136,8 @@ object HomePriorityEngine {
|
|||
Priority.GAME_WAITING -> input.gameWaiting
|
||||
Priority.CHALLENGE_WAITING -> input.challengeWaiting
|
||||
Priority.DAILY_QUESTION_UNANSWERED -> input.dailyQuestionUnanswered
|
||||
Priority.DAILY_QUESTION_AWAITING_PARTNER -> input.dailyQuestionAwaitingPartner
|
||||
Priority.DAILY_QUESTION_REVEALED -> input.dailyQuestionRevealed
|
||||
Priority.WEEKLY_RECAP_READY -> input.weeklyRecapReady
|
||||
Priority.CAPSULE_UNLOCKED -> input.capsuleUnlocked
|
||||
Priority.DATE_REMINDER -> input.dateReminder
|
||||
|
|
@ -154,6 +163,8 @@ object HomePriorityEngine {
|
|||
*/
|
||||
private fun Priority.isValueAction(): Boolean = when (this) {
|
||||
Priority.DAILY_QUESTION_UNANSWERED,
|
||||
Priority.DAILY_QUESTION_AWAITING_PARTNER,
|
||||
Priority.DAILY_QUESTION_REVEALED,
|
||||
Priority.WEEKLY_RECAP_READY,
|
||||
Priority.DATE_REMINDER -> true
|
||||
else -> false
|
||||
|
|
|
|||
|
|
@ -567,6 +567,8 @@ class HomeViewModel @Inject constructor(
|
|||
gameWaiting = hasWaitingGame(),
|
||||
challengeWaiting = hasIncompleteChallenge(),
|
||||
dailyQuestionUnanswered = dailyQuestionState == DailyQuestionState.UNANSWERED && dailyQuestion != null,
|
||||
dailyQuestionAwaitingPartner = dailyQuestionState == DailyQuestionState.USER_ANSWERED_PARTNER_PENDING,
|
||||
dailyQuestionRevealed = dailyQuestionState == DailyQuestionState.REVEALED && dailyQuestion != null,
|
||||
weeklyRecapReady = weeklyRecapReady,
|
||||
capsuleUnlocked = hasUnlockedCapsule(),
|
||||
dateReminder = hasUpcomingDate(),
|
||||
|
|
@ -666,6 +668,23 @@ class HomeViewModel @Inject constructor(
|
|||
cta = "Answer privately"
|
||||
)
|
||||
|
||||
// You answered; the reveal waits on your partner. The hero card (PrimaryHomeActionCard) overrides
|
||||
// this title/body and routes the CTA to the gentle-reminder send; this copy/CTA label is what shows
|
||||
// when it renders as a smaller secondary card (a game/challenge is the hero).
|
||||
Priority.DAILY_QUESTION_AWAITING_PARTNER -> buildDailyQuestionAction(
|
||||
title = "You showed up tonight.",
|
||||
body = partnerName?.let { "Your answer stays private until $it answers too — no pressure." }
|
||||
?: "Your answer stays private until your partner answers too — no pressure.",
|
||||
cta = "Send a gentle nudge"
|
||||
)
|
||||
|
||||
// You already revealed today — a low-priority closure card that links to the discussion thread.
|
||||
Priority.DAILY_QUESTION_REVEALED -> buildDailyQuestionAction(
|
||||
title = "You opened a conversation tonight.",
|
||||
body = "Keep it going whenever you're both ready.",
|
||||
cta = "Keep the conversation going"
|
||||
)
|
||||
|
||||
Priority.WEEKLY_RECAP_READY -> HomeAction(
|
||||
eyebrow = "Your week together",
|
||||
title = "Look back at what you built this week.",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import app.closer.ui.home.HomePriorityEngine.Input
|
|||
import app.closer.ui.home.HomePriorityEngine.Priority
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class HomePriorityEngineTest {
|
||||
|
|
@ -122,6 +123,80 @@ class HomePriorityEngineTest {
|
|||
assertEquals(Priority.DAILY_QUESTION_UNANSWERED, output.primary?.priority)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `awaiting partner is primary when no blockers`() {
|
||||
val input = Input(
|
||||
isPaired = true,
|
||||
dailyQuestionAwaitingPartner = true
|
||||
)
|
||||
|
||||
val output = HomePriorityEngine.compute(input)
|
||||
|
||||
assertEquals(Priority.DAILY_QUESTION_AWAITING_PARTNER, output.primary?.priority)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `game and challenge waiting outrank awaiting partner, which drops to a secondary card`() {
|
||||
val input = Input(
|
||||
isPaired = true,
|
||||
gameWaiting = true,
|
||||
challengeWaiting = true,
|
||||
dailyQuestionAwaitingPartner = true
|
||||
)
|
||||
|
||||
val output = HomePriorityEngine.compute(input)
|
||||
|
||||
// Ritual band: an actionable waiting game is the hero; "you answered, waiting" still shows below.
|
||||
assertEquals(Priority.GAME_WAITING, output.primary?.priority)
|
||||
assertTrue(output.secondary.any { it.priority == Priority.DAILY_QUESTION_AWAITING_PARTNER })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `awaiting partner outranks weekly recap`() {
|
||||
val input = Input(
|
||||
isPaired = true,
|
||||
dailyQuestionAwaitingPartner = true,
|
||||
weeklyRecapReady = true
|
||||
)
|
||||
|
||||
val output = HomePriorityEngine.compute(input)
|
||||
|
||||
assertEquals(Priority.DAILY_QUESTION_AWAITING_PARTNER, output.primary?.priority)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `revealed closure ranks below date reminder but surfaces as a secondary value card`() {
|
||||
val input = Input(
|
||||
isPaired = true,
|
||||
dateReminder = true,
|
||||
dailyQuestionRevealed = true,
|
||||
suggestedPackAvailable = true
|
||||
)
|
||||
|
||||
val output = HomePriorityEngine.compute(input)
|
||||
|
||||
assertEquals(Priority.DATE_REMINDER, output.primary?.priority)
|
||||
// Revealed is a value action (kept); the generic suggested pack is filtered out of secondary.
|
||||
assertEquals(
|
||||
listOf(Priority.DAILY_QUESTION_REVEALED),
|
||||
output.secondary.map { it.priority }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `revealed closure outranks generic browse content`() {
|
||||
val input = Input(
|
||||
isPaired = true,
|
||||
dailyQuestionRevealed = true,
|
||||
suggestedPackAvailable = true,
|
||||
exploreGamesAvailable = true
|
||||
)
|
||||
|
||||
val output = HomePriorityEngine.compute(input)
|
||||
|
||||
assertEquals(Priority.DAILY_QUESTION_REVEALED, output.primary?.priority)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `weekly recap outranks capsule and date reminder`() {
|
||||
val input = Input(
|
||||
|
|
|
|||
Loading…
Reference in New Issue