diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/worldclock/WorldClockSource.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/worldclock/WorldClockSource.kt index 4511f61..f250453 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/worldclock/WorldClockSource.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/worldclock/WorldClockSource.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map @@ -64,7 +63,7 @@ internal class WorldClockSource @Inject constructor( * state and `distinctUntilChanged` only drops the odd early wake (D18). */ @OptIn(ExperimentalCoroutinesApi::class) - fun state(refreshes: Flow = emptyFlow()): Flow = + fun state(refreshes: Flow): Flow = combine( worldClocks.worldClocks(), storedHomeZoneId, diff --git a/app/src/test/java/de/jeanlucmakiola/clockula/data/prefs/ClockPrefsTest.kt b/app/src/test/java/de/jeanlucmakiola/clockula/data/prefs/ClockPrefsTest.kt index 43225db..c11a2c6 100644 --- a/app/src/test/java/de/jeanlucmakiola/clockula/data/prefs/ClockPrefsTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/clockula/data/prefs/ClockPrefsTest.kt @@ -90,6 +90,9 @@ class ClockPrefsTest { assertThat(awaitItem()).isEqualTo(ClockDefaults()) prefs.setWorldClockSeconds(true) + // Wait until the write is visible to a reader, so a re-emission of + // the defaults would have had its chance to arrive. + prefs.worldClockSeconds.first { it } expectNoEvents() cancel() diff --git a/app/src/test/java/de/jeanlucmakiola/clockula/ui/settings/SettingsViewModelTest.kt b/app/src/test/java/de/jeanlucmakiola/clockula/ui/settings/SettingsViewModelTest.kt index 2dd8c52..4391896 100644 --- a/app/src/test/java/de/jeanlucmakiola/clockula/ui/settings/SettingsViewModelTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/clockula/ui/settings/SettingsViewModelTest.kt @@ -119,7 +119,6 @@ class SettingsViewModelTest { viewModel.onSnoozeLimitChanged(2) viewModel.onVolumeRampChanged(30) viewModel.onDismissChallengeChanged(DismissChallenge.MATH) - viewModel.onWorldClockSecondsChanged(true) advanceUntilIdle() val defaults = (viewModel.state.value as SettingsUiState.Loaded).defaults @@ -128,9 +127,23 @@ class SettingsViewModelTest { assertThat(defaults.snoozeLimit).isEqualTo(2) assertThat(defaults.volumeRampSeconds).isEqualTo(30) assertThat(defaults.dismissChallenge).isEqualTo(DismissChallenge.MATH) - assertThat((viewModel.state.value as SettingsUiState.Loaded).worldClockSeconds).isTrue() } + @Test + fun `the world clock's seconds switch writes through, apart from the defaults`(@TempDir tempDir: Path) = + runTest(main.dispatcher) { + val viewModel = collecting(newViewModel(tempDir)) + advanceUntilIdle() + val defaultsBefore = (viewModel.state.value as SettingsUiState.Loaded).defaults + + viewModel.onWorldClockSecondsChanged(true) + advanceUntilIdle() + + val loaded = viewModel.state.value as SettingsUiState.Loaded + assertThat(loaded.worldClockSeconds).isTrue() + assertThat(loaded.defaults).isEqualTo(defaultsBefore) + } + @Test fun `choosing a default alarm ringtone writes through and closes the picker`(@TempDir tempDir: Path) = runTest(main.dispatcher) { diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 86f1f56..09a9585 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -592,7 +592,7 @@ one anywhere under `domain/` or `ui/`. ## 6. Preferences One DataStore file, `clockula_prefs`, behind floret-kit's typed `PrefStore`. -Four slices: +Six slices: | Slice | Keys | Owner | |---|---|---| @@ -1429,9 +1429,11 @@ nothing but `delay`, which makes it testable under `runTest`'s virtual clock. `alignedTicks(unit)` is the same seam for a readout of whole wall-clock units: after the immediate emission it waits `WallBoundaries.untilNext(now, unit)`, re-measured against `WallClock` after every wake, so it fires on the second or -minute boundary itself and a long `delay` or a set clock does not accumulate. -The world clock is its one caller (§16, "The cadence"), and it merges in -`ClockChanges`, the system's own word that the clock or zone moved, so a long +minute boundary itself and a `delay` that ran long does not accumulate. A +clock set mid-wait is only caught up once that wait ends, so a caller on a +long cadence restarts the flow on `ClockChanges`, the system's own word that +the clock or zone moved. The world clock is its one caller (§16, "The +cadence"), and does exactly that, so a long cadence never means a long wait for those. ### The dismiss challenge, and why it cannot strand anyone