mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Add some megaphones to encourage users to try backups.
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package org.thoughtcrime.securesms.megaphone
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.thoughtcrime.securesms.database.model.MegaphoneRecord
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones.Event
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
class BackupUpsellScheduleTest {
|
||||
|
||||
private val now = System.currentTimeMillis()
|
||||
|
||||
// -- First display (seenCount = 0) --
|
||||
|
||||
@Test
|
||||
fun `shows on first display when no other backup upsell seen`() {
|
||||
val schedule = schedule(emptyRecords())
|
||||
assertTrue(schedule.shouldDisplay(0, 0, 0, now))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shows on first display when other upsell was seen outside the threshold`() {
|
||||
val records = emptyRecords().toMutableMap().apply {
|
||||
put(Event.BACKUPS_GENERIC_UPSELL, record(Event.BACKUPS_GENERIC_UPSELL, lastSeen = now - 60.days.inWholeMilliseconds - 1))
|
||||
}
|
||||
val schedule = schedule(records)
|
||||
assertTrue(schedule.shouldDisplay(0, 0, 0, now))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `suppressed on first display when another upsell was seen within the threshold`() {
|
||||
val records = emptyRecords().toMutableMap().apply {
|
||||
put(Event.BACKUPS_GENERIC_UPSELL, record(Event.BACKUPS_GENERIC_UPSELL, lastSeen = now - 60.days.inWholeMilliseconds + 1.hours.inWholeMilliseconds))
|
||||
}
|
||||
val schedule = schedule(records)
|
||||
assertFalse(schedule.shouldDisplay(0, 0, 0, now))
|
||||
}
|
||||
|
||||
// -- Recurring gap logic (seenCount > 0) --
|
||||
|
||||
@Test
|
||||
fun `shows after gap elapsed since last seen`() {
|
||||
val gapMs = 60.days.inWholeMilliseconds
|
||||
val schedule = schedule(emptyRecords(), gapMs)
|
||||
assertTrue(schedule.shouldDisplay(1, now - gapMs - 1, 0, now))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `suppressed when gap has not elapsed since last seen`() {
|
||||
val gapMs = 60.days.inWholeMilliseconds
|
||||
val schedule = schedule(emptyRecords(), gapMs)
|
||||
assertFalse(schedule.shouldDisplay(1, now - gapMs + 1.hours.inWholeMilliseconds, 0, now))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `uses second gap for second snooze`() {
|
||||
val firstGap = 60.days.inWholeMilliseconds
|
||||
val secondGap = 120.days.inWholeMilliseconds
|
||||
val schedule = schedule(emptyRecords(), firstGap, secondGap)
|
||||
|
||||
// seenCount=2 -> uses gaps[1] = 120 days
|
||||
assertFalse(schedule.shouldDisplay(2, now - 100.days.inWholeMilliseconds, 0, now))
|
||||
assertTrue(schedule.shouldDisplay(2, now - secondGap - 1, 0, now))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `repeats last gap for high seen counts`() {
|
||||
val firstGap = 60.days.inWholeMilliseconds
|
||||
val secondGap = 120.days.inWholeMilliseconds
|
||||
val schedule = schedule(emptyRecords(), firstGap, secondGap)
|
||||
|
||||
// seenCount=5 -> clamps to gaps[1] = 120 days
|
||||
assertFalse(schedule.shouldDisplay(5, now - 100.days.inWholeMilliseconds, 0, now))
|
||||
assertTrue(schedule.shouldDisplay(5, now - secondGap - 1, 0, now))
|
||||
}
|
||||
|
||||
// -- Combined: cross-event snooze AND recurring gap --
|
||||
|
||||
@Test
|
||||
fun `cross-event snooze blocks even when recurring gap is satisfied`() {
|
||||
val gapMs = 60.days.inWholeMilliseconds
|
||||
val records = emptyRecords().toMutableMap().apply {
|
||||
put(Event.BACKUP_LOW_STORAGE_UPSELL, record(Event.BACKUP_LOW_STORAGE_UPSELL, lastSeen = now - 30.days.inWholeMilliseconds))
|
||||
}
|
||||
val schedule = schedule(records, gapMs)
|
||||
|
||||
// Own gap satisfied (last seen 90 days ago) but another upsell was seen 30 days ago
|
||||
assertFalse(schedule.shouldDisplay(1, now - 90.days.inWholeMilliseconds, 0, now))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shows when both cross-event snooze and recurring gap are satisfied`() {
|
||||
val gapMs = 60.days.inWholeMilliseconds
|
||||
val records = emptyRecords().toMutableMap().apply {
|
||||
put(Event.BACKUP_LOW_STORAGE_UPSELL, record(Event.BACKUP_LOW_STORAGE_UPSELL, lastSeen = now - 60.days.inWholeMilliseconds - 1))
|
||||
}
|
||||
val schedule = schedule(records, gapMs)
|
||||
assertTrue(schedule.shouldDisplay(1, now - gapMs - 1, 0, now))
|
||||
}
|
||||
|
||||
// -- Ignores non-backup events --
|
||||
|
||||
@Test
|
||||
fun `ignores non-backup upsell events in cross-event check`() {
|
||||
val records = emptyRecords().toMutableMap().apply {
|
||||
put(Event.PIN_REMINDER, record(Event.PIN_REMINDER, lastSeen = now))
|
||||
put(Event.NOTIFICATIONS, record(Event.NOTIFICATIONS, lastSeen = now))
|
||||
}
|
||||
val schedule = schedule(records)
|
||||
assertTrue(schedule.shouldDisplay(0, 0, 0, now))
|
||||
}
|
||||
|
||||
// -- Helpers --
|
||||
|
||||
private fun schedule(records: Map<Event, MegaphoneRecord>, vararg gaps: Long): BackupUpsellSchedule {
|
||||
return BackupUpsellSchedule(records, *gaps)
|
||||
}
|
||||
|
||||
private fun record(event: Event, seenCount: Int = 1, lastSeen: Long = 0, firstVisible: Long = 0, finished: Boolean = false): MegaphoneRecord {
|
||||
return MegaphoneRecord(event, seenCount, lastSeen, firstVisible, finished)
|
||||
}
|
||||
|
||||
private fun emptyRecords(): Map<Event, MegaphoneRecord> {
|
||||
return BackupUpsellSchedule.BACKUP_UPSELL_EVENTS.associateWith { record(it, seenCount = 0) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user