Port iOS TaskSuggestionsView as a standalone route reachable outside onboarding. Uses shared suggestions API + accept/skip analytics in non-onboarding variant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
3.0 KiB
Kotlin
92 lines
3.0 KiB
Kotlin
package com.tt.honeyDue.notifications
|
|
|
|
import android.app.AlarmManager
|
|
import android.content.Context
|
|
import android.os.Build
|
|
import androidx.test.core.app.ApplicationProvider
|
|
import org.junit.Assert.assertEquals
|
|
import org.junit.Assert.assertTrue
|
|
import org.junit.Before
|
|
import org.junit.Test
|
|
import org.junit.runner.RunWith
|
|
import org.robolectric.RobolectricTestRunner
|
|
import org.robolectric.Shadows.shadowOf
|
|
import org.robolectric.annotation.Config
|
|
|
|
/**
|
|
* Tests for [SnoozeScheduler] — verifies the AlarmManager scheduling path
|
|
* used by the P4 Stream O notification Snooze action.
|
|
*/
|
|
@RunWith(RobolectricTestRunner::class)
|
|
@Config(sdk = [Build.VERSION_CODES.TIRAMISU])
|
|
class SnoozeSchedulerTest {
|
|
|
|
private lateinit var context: Context
|
|
private lateinit var am: AlarmManager
|
|
|
|
@Before
|
|
fun setUp() {
|
|
context = ApplicationProvider.getApplicationContext()
|
|
am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
|
// Robolectric's ShadowAlarmManager doesn't have an explicit clear, but
|
|
// scheduledAlarms is filtered by live pending intents so cancel() the
|
|
// world before each test.
|
|
shadowOf(am).scheduledAlarms.toList().forEach { alarm ->
|
|
alarm.operation?.let { am.cancel(it) }
|
|
}
|
|
}
|
|
|
|
// ---------- 7. schedule() sets alarm 30 minutes in future ----------
|
|
|
|
@Test
|
|
fun schedule_setsAlarmThirtyMinutesInFuture() {
|
|
val before = System.currentTimeMillis()
|
|
SnoozeScheduler.schedule(
|
|
context = context,
|
|
taskId = 123L,
|
|
title = "t",
|
|
body = "b",
|
|
type = NotificationChannels.TASK_REMINDER
|
|
)
|
|
|
|
val scheduled = shadowOf(am).scheduledAlarms
|
|
assertEquals(1, scheduled.size)
|
|
val delta = scheduled.first().triggerAtTime - before
|
|
val expected = NotificationActions.SNOOZE_DELAY_MS
|
|
assertTrue(
|
|
"expected ~30 min trigger, got delta=$delta",
|
|
delta in (expected - 2_000)..(expected + 2_000)
|
|
)
|
|
}
|
|
|
|
// ---------- 8. cancel() removes the pending alarm ----------
|
|
|
|
@Test
|
|
fun cancel_preventsLaterDelivery() {
|
|
SnoozeScheduler.schedule(context, taskId = 456L)
|
|
assertEquals(
|
|
"precondition: alarm scheduled",
|
|
1,
|
|
shadowOf(am).scheduledAlarms.size
|
|
)
|
|
|
|
SnoozeScheduler.cancel(context, taskId = 456L)
|
|
|
|
// After cancel(), the PendingIntent is consumed so scheduledAlarms
|
|
// shrinks back to zero (Robolectric matches by PI equality).
|
|
assertEquals(
|
|
"alarm should be gone after cancel()",
|
|
0,
|
|
shadowOf(am).scheduledAlarms.size
|
|
)
|
|
}
|
|
|
|
// Bonus coverage: different task ids get independent scheduling slots.
|
|
@Test
|
|
fun schedule_twoDifferentTasks_yieldsTwoAlarms() {
|
|
SnoozeScheduler.schedule(context, taskId = 1L)
|
|
SnoozeScheduler.schedule(context, taskId = 2L)
|
|
assertEquals(2, shadowOf(am).scheduledAlarms.size)
|
|
}
|
|
}
|