feat(shell): navigation the system can ask for, read once per launch
An incoming request is more than a tab now — it can open an editor, compose a timer or ask which alarm to dismiss. So it is read once, when the activity is created rather than on every configuration change: re-deriving it on a rotation would drag the user back into an editor they had just left. The extras are unparcelled behind a guard here too. `MainActivity` is exported, and before this it only ever read the action, which never unparcels.
This commit is contained in:
1 parent
e55f3d19d4
commit
02f3794ee8
5 files changed
+268
-23
No files matched your search
@@ -0,0 +1,125 @@
|
||||
package de.jeanlucmakiola.clockula.ui.shell
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import de.jeanlucmakiola.clockula.alarm.AlarmIntents
|
||||
import de.jeanlucmakiola.clockula.domain.interop.AlarmClockContract
|
||||
import de.jeanlucmakiola.clockula.interop.InteropIntents
|
||||
import de.jeanlucmakiola.clockula.stopwatch.StopwatchIntents
|
||||
import de.jeanlucmakiola.clockula.timer.TimerIntents
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* The shell's intent surface, now that three of the contract's outcomes are not
|
||||
* "select a tab". `requestFor` **degrades rather than throws**: an intent that
|
||||
* reaches `MainActivity` malformed must still put the user somewhere sensible
|
||||
* (M9 D22, D23).
|
||||
*/
|
||||
class ShellNavigationInteropTest {
|
||||
|
||||
@Test
|
||||
fun `the internal show-alarms action selects the alarms tab`() {
|
||||
assertThat(ShellNavigation.tabForAction(AlarmIntents.ACTION_SHOW_ALARMS))
|
||||
.isEqualTo(ClockulaDestination.ALARMS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the tabs the earlier milestones wired still answer`() {
|
||||
val tabs = listOf(
|
||||
ShellNavigation.tabForAction(TimerIntents.ACTION_SHOW_TIMERS),
|
||||
ShellNavigation.tabForAction(StopwatchIntents.ACTION_SHOW_STOPWATCH),
|
||||
ShellNavigation.tabForAction(null),
|
||||
ShellNavigation.tabForAction("com.example.X"),
|
||||
)
|
||||
|
||||
assertThat(tabs).containsExactly(
|
||||
ClockulaDestination.TIMERS,
|
||||
ClockulaDestination.STOPWATCH,
|
||||
null,
|
||||
null,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an editor request carries the alarm id`() {
|
||||
assertThat(ShellNavigation.requestFor(InteropIntents.ACTION_OPEN_ALARM_EDITOR, 7L, emptyList()))
|
||||
.isEqualTo(ShellRequest.OpenAlarmEditor(7L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an editor request with no id lands on the alarms tab`() {
|
||||
assertThat(ShellNavigation.requestFor(InteropIntents.ACTION_OPEN_ALARM_EDITOR, null, emptyList()))
|
||||
.isEqualTo(ShellRequest.OpenTab(ClockulaDestination.ALARMS))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an editor request with a non-positive id lands on the alarms tab`() {
|
||||
val requests = listOf(
|
||||
ShellNavigation.requestFor(InteropIntents.ACTION_OPEN_ALARM_EDITOR, 0L, emptyList()),
|
||||
ShellNavigation.requestFor(InteropIntents.ACTION_OPEN_ALARM_EDITOR, -4L, emptyList()),
|
||||
)
|
||||
|
||||
assertThat(requests).containsExactly(
|
||||
ShellRequest.OpenTab(ClockulaDestination.ALARMS),
|
||||
ShellRequest.OpenTab(ClockulaDestination.ALARMS),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a compose-timer request needs no extra`() {
|
||||
assertThat(ShellNavigation.requestFor(InteropIntents.ACTION_COMPOSE_TIMER, null, emptyList()))
|
||||
.isEqualTo(ShellRequest.ComposeTimer)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a chooser request keeps its ids in order`() {
|
||||
assertThat(
|
||||
ShellNavigation.requestFor(
|
||||
InteropIntents.ACTION_CHOOSE_ALARM_TO_DISMISS,
|
||||
null,
|
||||
listOf(3L, 1L),
|
||||
),
|
||||
).isEqualTo(ShellRequest.ChooseAlarmToDismiss(listOf(3L, 1L)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a chooser request with nothing to choose from lands on the alarms tab`() {
|
||||
assertThat(
|
||||
ShellNavigation.requestFor(InteropIntents.ACTION_CHOOSE_ALARM_TO_DISMISS, null, emptyList()),
|
||||
).isEqualTo(ShellRequest.OpenTab(ClockulaDestination.ALARMS))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a tab action becomes a tab request`() {
|
||||
assertThat(ShellNavigation.requestFor(TimerIntents.ACTION_SHOW_TIMERS, null, emptyList()))
|
||||
.isEqualTo(ShellRequest.OpenTab(ClockulaDestination.TIMERS))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a null action asks for nothing`() {
|
||||
assertThat(ShellNavigation.requestFor(null, 7L, listOf(1L))).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unknown action is never rescued by its extras`() {
|
||||
assertThat(ShellNavigation.requestFor("com.example.WHATEVER", 7L, listOf(1L))).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the internal transport never collides with another vocabulary`() {
|
||||
val others = AlarmClockContract.ACTIONS + listOf(
|
||||
AlarmIntents.ACTION_FIRE,
|
||||
AlarmIntents.ACTION_AUTO_SILENCE,
|
||||
AlarmIntents.ACTION_SNOOZE,
|
||||
AlarmIntents.ACTION_DISMISS,
|
||||
AlarmIntents.ACTION_SHOW_ALARMS,
|
||||
TimerIntents.ACTION_EXPIRY,
|
||||
TimerIntents.ACTION_SHOW_TIMERS,
|
||||
TimerIntents.ACTION_SERVICE_STOP,
|
||||
StopwatchIntents.ACTION_SHOW_STOPWATCH,
|
||||
StopwatchIntents.ACTION_SERVICE_STOP,
|
||||
)
|
||||
|
||||
assertThat(InteropIntents.ACTIONS.intersect(others.toSet())).isEmpty()
|
||||
assertThat(InteropIntents.ACTIONS).containsNoDuplicates()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user