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:
1 parent
02f3794ee8
commit
46a049f988
9 files changed
+420
-1
No files matched your search
@@ -345,4 +345,55 @@ class AlarmsViewModelTest {
|
||||
|
||||
assertThat(viewModel.state.value.rows().map { it.id }).containsExactly(2L)
|
||||
}
|
||||
|
||||
// --- M9: the AlarmClock contract's dismissal, and the chooser dialog's ---
|
||||
|
||||
@Test
|
||||
fun `dismissing a ringing alarm from the dialog closes the cycle`(@TempDir tempDir: Path) =
|
||||
runTest(main.dispatcher) {
|
||||
val harness = alarmEngineHarness(tempDir)
|
||||
harness.givenAlarm(id = 1L, hour = 7)
|
||||
harness.engine.reschedule()
|
||||
harness.givenRinging(alarmId = 1L, at = sevenToday)
|
||||
val viewModel = viewModel(harness)
|
||||
advanceUntilIdle()
|
||||
|
||||
viewModel.onDismissUpcoming(1L)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertThat(harness.states.state(1L).ringingSince).isNull()
|
||||
assertThat(harness.ring.events.last()).isEqualTo(RingEvent.Stopped(1L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dismissing a repeating alarm only skips its next occurrence`(@TempDir tempDir: Path) =
|
||||
runTest(main.dispatcher) {
|
||||
val harness = alarmEngineHarness(tempDir)
|
||||
harness.givenAlarm(id = 1L, hour = 7, repeatDays = RepeatDays.EVERY_DAY)
|
||||
harness.engine.reschedule()
|
||||
val viewModel = viewModel(harness)
|
||||
advanceUntilIdle()
|
||||
|
||||
viewModel.onDismissUpcoming(1L)
|
||||
advanceUntilIdle()
|
||||
|
||||
val alarm = harness.alarms.find(1L)!!
|
||||
assertThat(alarm.skipNextOccurrence to alarm.enabled).isEqualTo(true to true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dismissing an id no alarm has writes nothing`(@TempDir tempDir: Path) =
|
||||
runTest(main.dispatcher) {
|
||||
val harness = alarmEngineHarness(tempDir)
|
||||
harness.givenAlarm(id = 1L, hour = 7)
|
||||
harness.engine.reschedule()
|
||||
val viewModel = viewModel(harness)
|
||||
advanceUntilIdle()
|
||||
val before = harness.alarmDao.stored
|
||||
|
||||
viewModel.onDismissUpcoming(404L)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertThat(harness.alarmDao.stored).isEqualTo(before)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package de.jeanlucmakiola.clockula.ui.alarms
|
||||
|
||||
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 org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* The chooser dialog filters the list already on screen, so the question and
|
||||
* the list behind it agree — one source, one order, no second ticker (M9 D13).
|
||||
*/
|
||||
class DismissChooserTest {
|
||||
|
||||
private fun row(id: Long, hour: Int) = AlarmRowState(
|
||||
id = id,
|
||||
time = TimeOfDay(hour, 0),
|
||||
label = "",
|
||||
enabled = true,
|
||||
repeatDays = RepeatDays.NONE,
|
||||
nextFire = NextFireLabel.NotScheduled,
|
||||
snoozed = false,
|
||||
ringing = false,
|
||||
skippingNext = false,
|
||||
isNext = false,
|
||||
)
|
||||
|
||||
private val rows = listOf(row(1L, 6), row(2L, 7), row(3L, 8))
|
||||
|
||||
@Test
|
||||
fun `the chosen rows keep the list's own order, not the candidates'`() {
|
||||
assertThat(DismissChooser.rowsFor(rows, listOf(3L, 1L)).map { it.id })
|
||||
.containsExactly(1L, 3L).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a candidate the list does not hold is skipped`() {
|
||||
assertThat(DismissChooser.rowsFor(rows, listOf(2L, 99L)).map { it.id }).containsExactly(2L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no candidates means no rows`() {
|
||||
assertThat(DismissChooser.rowsFor(rows, emptyList())).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a duplicated candidate appears once`() {
|
||||
assertThat(DismissChooser.rowsFor(rows, listOf(2L, 2L, 2L)).map { it.id }).containsExactly(2L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an empty list answers with nothing and throws nothing`() {
|
||||
assertThat(DismissChooser.rowsFor(emptyList(), listOf(1L, 2L))).isEmpty()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user