feat(alarms): ask which alarm, when the request names more than one
"Dismiss my alarm" with two alarms set is a question, not an instruction. When the search matches more than one the app asks, in a dialog listing the candidates; when it matches exactly one it acts without asking. The Timers tab's setup effect no longer re-keys on a list that changes with every readout tick.
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
package de.jeanlucmakiola.clockula.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.AlarmClock
|
||||
import androidx.compose.ui.test.junit4.v2.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import de.jeanlucmakiola.clockula.domain.RepeatDays
|
||||
import de.jeanlucmakiola.clockula.domain.TimeOfDay
|
||||
import de.jeanlucmakiola.clockula.domain.format.NextFireLabel
|
||||
import de.jeanlucmakiola.clockula.domain.interop.AlarmClockContract
|
||||
import de.jeanlucmakiola.clockula.interop.AlarmClockActivity
|
||||
import de.jeanlucmakiola.clockula.ui.alarms.AlarmRowState
|
||||
import de.jeanlucmakiola.clockula.ui.alarms.DismissAlarmDialog
|
||||
import de.jeanlucmakiola.clockula.ui.theme.ClockulaTheme
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* What only a device can answer about the `AlarmClock` contract: that our
|
||||
* literals really are the platform's constants, that the manifest's two
|
||||
* intent-filters really resolve — with and without a data URI — and that the
|
||||
* door really opens no window.
|
||||
*
|
||||
* Compiled in the gate; **not run here** — no device is attached.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class InteropInstrumentedTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
private fun row(id: Long, hour: Int, label: String) = AlarmRowState(
|
||||
id = id,
|
||||
time = TimeOfDay(hour, 0),
|
||||
label = label,
|
||||
enabled = true,
|
||||
repeatDays = RepeatDays.NONE,
|
||||
nextFire = NextFireLabel.NotScheduled,
|
||||
snoozed = false,
|
||||
ringing = false,
|
||||
skippingNext = false,
|
||||
isNext = false,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun ourLiteralsAreThePlatformsOwnConstants() {
|
||||
val ours = listOf(
|
||||
AlarmClockContract.ACTION_SET_ALARM,
|
||||
AlarmClockContract.ACTION_SET_TIMER,
|
||||
AlarmClockContract.ACTION_SHOW_ALARMS,
|
||||
AlarmClockContract.ACTION_SHOW_TIMERS,
|
||||
AlarmClockContract.ACTION_DISMISS_ALARM,
|
||||
AlarmClockContract.ACTION_DISMISS_TIMER,
|
||||
AlarmClockContract.ACTION_SNOOZE_ALARM,
|
||||
AlarmClockContract.EXTRA_HOUR,
|
||||
AlarmClockContract.EXTRA_MINUTES,
|
||||
AlarmClockContract.EXTRA_MESSAGE,
|
||||
AlarmClockContract.EXTRA_DAYS,
|
||||
AlarmClockContract.EXTRA_RINGTONE,
|
||||
AlarmClockContract.EXTRA_VIBRATE,
|
||||
AlarmClockContract.EXTRA_SKIP_UI,
|
||||
AlarmClockContract.EXTRA_LENGTH,
|
||||
AlarmClockContract.EXTRA_IS_PM,
|
||||
AlarmClockContract.EXTRA_ALARM_SEARCH_MODE,
|
||||
AlarmClockContract.EXTRA_ALARM_SNOOZE_DURATION,
|
||||
AlarmClockContract.SEARCH_MODE_TIME,
|
||||
AlarmClockContract.SEARCH_MODE_NEXT,
|
||||
AlarmClockContract.SEARCH_MODE_ALL,
|
||||
AlarmClockContract.SEARCH_MODE_LABEL,
|
||||
AlarmClockContract.VALUE_RINGTONE_SILENT,
|
||||
)
|
||||
|
||||
assertThat(ours).containsExactly(
|
||||
AlarmClock.ACTION_SET_ALARM,
|
||||
AlarmClock.ACTION_SET_TIMER,
|
||||
AlarmClock.ACTION_SHOW_ALARMS,
|
||||
AlarmClock.ACTION_SHOW_TIMERS,
|
||||
AlarmClock.ACTION_DISMISS_ALARM,
|
||||
AlarmClock.ACTION_DISMISS_TIMER,
|
||||
AlarmClock.ACTION_SNOOZE_ALARM,
|
||||
AlarmClock.EXTRA_HOUR,
|
||||
AlarmClock.EXTRA_MINUTES,
|
||||
AlarmClock.EXTRA_MESSAGE,
|
||||
AlarmClock.EXTRA_DAYS,
|
||||
AlarmClock.EXTRA_RINGTONE,
|
||||
AlarmClock.EXTRA_VIBRATE,
|
||||
AlarmClock.EXTRA_SKIP_UI,
|
||||
AlarmClock.EXTRA_LENGTH,
|
||||
AlarmClock.EXTRA_IS_PM,
|
||||
AlarmClock.EXTRA_ALARM_SEARCH_MODE,
|
||||
AlarmClock.EXTRA_ALARM_SNOOZE_DURATION,
|
||||
AlarmClock.ALARM_SEARCH_MODE_TIME,
|
||||
AlarmClock.ALARM_SEARCH_MODE_NEXT,
|
||||
AlarmClock.ALARM_SEARCH_MODE_ALL,
|
||||
AlarmClock.ALARM_SEARCH_MODE_LABEL,
|
||||
AlarmClock.VALUE_RINGTONE_SILENT,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun everyContractActionResolvesToTheDoor() {
|
||||
val unresolved = AlarmClockContract.ACTIONS.filterNot { action ->
|
||||
context.packageManager
|
||||
.queryIntentActivities(Intent(action).setPackage(context.packageName), 0)
|
||||
.any { it.activityInfo.name == AlarmClockActivity::class.java.name }
|
||||
}
|
||||
|
||||
assertThat(unresolved).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aDeeplinkedDismissResolvesToTheDoor() {
|
||||
val intent = Intent(AlarmClockContract.ACTION_DISMISS_ALARM)
|
||||
.setPackage(context.packageName)
|
||||
.setData(Uri.parse("clockula://alarm/1"))
|
||||
|
||||
val resolved = context.packageManager.queryIntentActivities(intent, 0)
|
||||
|
||||
assertThat(resolved.map { it.activityInfo.name })
|
||||
.contains(AlarmClockActivity::class.java.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tappingARowInTheChooserDismissesThatAlarm() {
|
||||
val dismissed = mutableListOf<Long>()
|
||||
composeTestRule.setContent {
|
||||
ClockulaTheme {
|
||||
DismissAlarmDialog(
|
||||
rows = listOf(row(1L, 6, "Morning gym"), row(2L, 7, "Yoga")),
|
||||
onDismissAlarm = { dismissed += it },
|
||||
onCancel = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText("Yoga").assertExists()
|
||||
composeTestRule.onNodeWithText("Morning gym").performClick()
|
||||
|
||||
assertThat(dismissed).containsExactly(1L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theDoorFinishesWithoutEverShowingAWindow() {
|
||||
val intent = Intent(context, AlarmClockActivity::class.java)
|
||||
.setAction(AlarmClockContract.ACTION_SHOW_ALARMS)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
ActivityScenario.launch<AlarmClockActivity>(intent).use { scenario ->
|
||||
assertThat(scenario.state).isEqualTo(Lifecycle.State.DESTROYED)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user