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 gameWaiting: Boolean = false,
|
||||||
val challengeWaiting: Boolean = false,
|
val challengeWaiting: Boolean = false,
|
||||||
val dailyQuestionUnanswered: Boolean = false,
|
val dailyQuestionUnanswered: Boolean = false,
|
||||||
|
val dailyQuestionAwaitingPartner: Boolean = false,
|
||||||
|
val dailyQuestionRevealed: Boolean = false,
|
||||||
val weeklyRecapReady: Boolean = false,
|
val weeklyRecapReady: Boolean = false,
|
||||||
val capsuleUnlocked: Boolean = false,
|
val capsuleUnlocked: Boolean = false,
|
||||||
val dateReminder: Boolean = false,
|
val dateReminder: Boolean = false,
|
||||||
|
|
@ -60,9 +62,14 @@ object HomePriorityEngine {
|
||||||
GAME_WAITING,
|
GAME_WAITING,
|
||||||
DAILY_QUESTION_UNANSWERED,
|
DAILY_QUESTION_UNANSWERED,
|
||||||
CHALLENGE_WAITING,
|
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,
|
WEEKLY_RECAP_READY,
|
||||||
CAPSULE_UNLOCKED,
|
CAPSULE_UNLOCKED,
|
||||||
DATE_REMINDER,
|
DATE_REMINDER,
|
||||||
|
// You already revealed today's question — a low-priority "keep the conversation going" closure card.
|
||||||
|
DAILY_QUESTION_REVEALED,
|
||||||
SUGGESTED_PACK,
|
SUGGESTED_PACK,
|
||||||
EXPLORE_GAMES
|
EXPLORE_GAMES
|
||||||
}
|
}
|
||||||
|
|
@ -129,6 +136,8 @@ object HomePriorityEngine {
|
||||||
Priority.GAME_WAITING -> input.gameWaiting
|
Priority.GAME_WAITING -> input.gameWaiting
|
||||||
Priority.CHALLENGE_WAITING -> input.challengeWaiting
|
Priority.CHALLENGE_WAITING -> input.challengeWaiting
|
||||||
Priority.DAILY_QUESTION_UNANSWERED -> input.dailyQuestionUnanswered
|
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.WEEKLY_RECAP_READY -> input.weeklyRecapReady
|
||||||
Priority.CAPSULE_UNLOCKED -> input.capsuleUnlocked
|
Priority.CAPSULE_UNLOCKED -> input.capsuleUnlocked
|
||||||
Priority.DATE_REMINDER -> input.dateReminder
|
Priority.DATE_REMINDER -> input.dateReminder
|
||||||
|
|
@ -154,6 +163,8 @@ object HomePriorityEngine {
|
||||||
*/
|
*/
|
||||||
private fun Priority.isValueAction(): Boolean = when (this) {
|
private fun Priority.isValueAction(): Boolean = when (this) {
|
||||||
Priority.DAILY_QUESTION_UNANSWERED,
|
Priority.DAILY_QUESTION_UNANSWERED,
|
||||||
|
Priority.DAILY_QUESTION_AWAITING_PARTNER,
|
||||||
|
Priority.DAILY_QUESTION_REVEALED,
|
||||||
Priority.WEEKLY_RECAP_READY,
|
Priority.WEEKLY_RECAP_READY,
|
||||||
Priority.DATE_REMINDER -> true
|
Priority.DATE_REMINDER -> true
|
||||||
else -> false
|
else -> false
|
||||||
|
|
|
||||||
|
|
@ -567,6 +567,8 @@ class HomeViewModel @Inject constructor(
|
||||||
gameWaiting = hasWaitingGame(),
|
gameWaiting = hasWaitingGame(),
|
||||||
challengeWaiting = hasIncompleteChallenge(),
|
challengeWaiting = hasIncompleteChallenge(),
|
||||||
dailyQuestionUnanswered = dailyQuestionState == DailyQuestionState.UNANSWERED && dailyQuestion != null,
|
dailyQuestionUnanswered = dailyQuestionState == DailyQuestionState.UNANSWERED && dailyQuestion != null,
|
||||||
|
dailyQuestionAwaitingPartner = dailyQuestionState == DailyQuestionState.USER_ANSWERED_PARTNER_PENDING,
|
||||||
|
dailyQuestionRevealed = dailyQuestionState == DailyQuestionState.REVEALED && dailyQuestion != null,
|
||||||
weeklyRecapReady = weeklyRecapReady,
|
weeklyRecapReady = weeklyRecapReady,
|
||||||
capsuleUnlocked = hasUnlockedCapsule(),
|
capsuleUnlocked = hasUnlockedCapsule(),
|
||||||
dateReminder = hasUpcomingDate(),
|
dateReminder = hasUpcomingDate(),
|
||||||
|
|
@ -666,6 +668,23 @@ class HomeViewModel @Inject constructor(
|
||||||
cta = "Answer privately"
|
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(
|
Priority.WEEKLY_RECAP_READY -> HomeAction(
|
||||||
eyebrow = "Your week together",
|
eyebrow = "Your week together",
|
||||||
title = "Look back at what you built this week.",
|
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 app.closer.ui.home.HomePriorityEngine.Priority
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class HomePriorityEngineTest {
|
class HomePriorityEngineTest {
|
||||||
|
|
@ -122,6 +123,80 @@ class HomePriorityEngineTest {
|
||||||
assertEquals(Priority.DAILY_QUESTION_UNANSWERED, output.primary?.priority)
|
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
|
@Test
|
||||||
fun `weekly recap outranks capsule and date reminder`() {
|
fun `weekly recap outranks capsule and date reminder`() {
|
||||||
val input = Input(
|
val input = Input(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue