P2 Stream H: standalone TaskSuggestionsScreen
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>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user