From d8e3250af943d05c4d634e8cffe99ae19dc518a5 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Thu, 15 Oct 2026 16:37:46 +0200 Subject: [PATCH] Share test event builders and repository wiring (#408) Step 3 of the simplification review. Tests only, no production code changes. - `TestEvents.kt`: defaulted `testEvent` / `allDayEvent` / `timedEvent` / `testDetail` builders replace the full `EventInstance(...)` / `EventDetail(...)` spellings and the per-file `allDay`/`timed` helpers. Helpers with test-specific titles, colours or zones stay as one-line wrappers, so every built event is unchanged. - `TestRepositories.kt`: `testDataStore` / `testPrefs` / `testSettings` / `testRepository` plus a JUnit 5 `MainDispatcherExtension` replace the repeated DataStore, repository and setMain/resetMain wiring. Same 1302 tests before and after; widget tests untouched. Co-authored-by: Jean-Luc Makiola Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/408 --- .../de/jeanlucmakiola/calendula/TestEvents.kt | 118 ++++++++++++++ .../calendula/TestRepositories.kt | 55 +++++++ .../calendar/CalendarRepositoryImplTest.kt | 146 ++++++++---------- .../contacts/SpecialDatesSyncEngineTest.kt | 13 +- .../calendula/data/prefs/CalendarPrefsTest.kt | 28 ++-- .../calendula/data/prefs/SettingsPrefsTest.kt | 122 +++++++-------- .../calendula/domain/EventFormTest.kt | 10 +- .../calendula/domain/EventInstanceSpanTest.kt | 14 +- .../calendula/domain/EventSearchTest.kt | 25 +-- .../ui/agenda/AgendaItemDatesTest.kt | 15 +- .../ui/agenda/AgendaTimeLabelTest.kt | 14 +- .../ui/agenda/GroupAgendaDaysTest.kt | 13 +- .../calendula/ui/common/IsPastOnTest.kt | 9 +- .../calendula/ui/common/OutOfViewEdgesTest.kt | 15 +- .../ui/common/RescheduleViewModelTest.kt | 52 +++---- .../ui/common/TimedBlockShapeTest.kt | 11 +- .../calendula/ui/common/TimelineDropTest.kt | 29 +--- .../calendula/ui/common/ViewMorphKeyTest.kt | 10 +- .../ui/detail/EventDetailViewModelTest.kt | 53 +++---- .../ui/edit/EventEditViewModelTest.kt | 50 +++--- .../calendula/ui/month/ChipAtCellYTest.kt | 32 +--- .../calendula/ui/month/ChipSeatTest.kt | 17 +- .../calendula/ui/month/ClipWeekToMonthTest.kt | 32 +--- .../calendula/ui/month/DayIndexTest.kt | 29 +--- .../calendula/ui/month/LaneEventsTest.kt | 31 +--- .../calendula/ui/month/MonthChipTimeTest.kt | 32 +--- .../calendula/ui/month/MonthLaneCapTest.kt | 17 +- .../calendula/ui/month/MonthLayoutTest.kt | 40 +---- .../ui/search/SearchViewModelTest.kt | 47 ++---- .../calendula/ui/week/ColumnGeometryTest.kt | 15 +- .../calendula/ui/week/WeekLayoutTest.kt | 14 +- 31 files changed, 465 insertions(+), 643 deletions(-) create mode 100644 app/src/test/java/de/jeanlucmakiola/calendula/TestEvents.kt create mode 100644 app/src/test/java/de/jeanlucmakiola/calendula/TestRepositories.kt diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/TestEvents.kt b/app/src/test/java/de/jeanlucmakiola/calendula/TestEvents.kt new file mode 100644 index 00000000..c14caa68 --- /dev/null +++ b/app/src/test/java/de/jeanlucmakiola/calendula/TestEvents.kt @@ -0,0 +1,118 @@ +package de.jeanlucmakiola.calendula + +import de.jeanlucmakiola.calendula.domain.AccessLevel +import de.jeanlucmakiola.calendula.domain.Attendee +import de.jeanlucmakiola.calendula.domain.AttendeeStatus +import de.jeanlucmakiola.calendula.domain.Availability +import de.jeanlucmakiola.calendula.domain.EventDetail +import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.domain.EventResponse +import de.jeanlucmakiola.calendula.domain.EventStatus +import de.jeanlucmakiola.calendula.domain.Reminder +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atTime +import kotlinx.datetime.plus +import kotlinx.datetime.toInstant +import kotlin.time.Instant + +/** An [EventInstance] with every field defaulted; [eventId] follows [id]. */ +fun testEvent( + id: Long = 1L, + eventId: Long = id, + calendarId: Long = 1L, + title: String = "Event", + start: Instant = Instant.fromEpochMilliseconds(0), + end: Instant = start, + isAllDay: Boolean = false, + color: Int = 0, + location: String? = null, + isRecurring: Boolean = false, + response: EventResponse = EventResponse.Going, +): EventInstance = EventInstance( + instanceId = id, + eventId = eventId, + calendarId = calendarId, + title = title, + start = start, + end = end, + isAllDay = isAllDay, + color = color, + location = location, + isRecurring = isRecurring, + response = response, +) + +/** An all-day event over [from]..[toInclusive], at midnights in [zone] with an exclusive end. */ +fun allDayEvent( + from: LocalDate, + toInclusive: LocalDate = from, + id: Long = 1L, + title: String = "Event", + color: Int = 0, + zone: TimeZone = TimeZone.UTC, + calendarId: Long = 1L, +): EventInstance = testEvent( + id = id, + calendarId = calendarId, + title = title, + start = from.atTime(0, 0).toInstant(zone), + end = toInclusive.plus(1, DateTimeUnit.DAY).atTime(0, 0).toInstant(zone), + isAllDay = true, + color = color, +) + +/** A timed event from [date] [startHour]:[minute] to [endDate] [endHour]:[minute] in [zone]. */ +fun timedEvent( + date: LocalDate, + startHour: Int, + endHour: Int = startHour + 1, + zone: TimeZone = TimeZone.UTC, + id: Long = 1L, + title: String = "Event", + color: Int = 0, + endDate: LocalDate = date, + minute: Int = 0, + calendarId: Long = 1L, +): EventInstance = testEvent( + id = id, + calendarId = calendarId, + title = title, + start = date.atTime(startHour, minute).toInstant(zone), + end = endDate.atTime(endHour, minute).toInstant(zone), + color = color, +) + +/** An [EventDetail] around [instance] with every other field defaulted. */ +fun testDetail( + instance: EventInstance = testEvent(), + description: String? = null, + organizer: String? = null, + attendees: List = emptyList(), + rrule: String? = null, + reminders: List = emptyList(), + status: EventStatus = EventStatus.Confirmed, + availability: Availability = Availability.Busy, + accessLevel: AccessLevel = AccessLevel.Default, + eventTimezone: String? = null, + selfStatus: AttendeeStatus = AttendeeStatus.Unknown, + eventColor: Int? = null, + eventColorKey: String? = null, + isException: Boolean = false, +): EventDetail = EventDetail( + instance = instance, + description = description, + organizer = organizer, + attendees = attendees, + rrule = rrule, + reminders = reminders, + status = status, + availability = availability, + accessLevel = accessLevel, + eventTimezone = eventTimezone, + selfStatus = selfStatus, + eventColor = eventColor, + eventColorKey = eventColorKey, + isException = isException, +) diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/TestRepositories.kt b/app/src/test/java/de/jeanlucmakiola/calendula/TestRepositories.kt new file mode 100644 index 00000000..51de87a3 --- /dev/null +++ b/app/src/test/java/de/jeanlucmakiola/calendula/TestRepositories.kt @@ -0,0 +1,55 @@ +package de.jeanlucmakiola.calendula + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import de.jeanlucmakiola.calendula.data.calendar.CalendarDataSource +import de.jeanlucmakiola.calendula.data.calendar.CalendarRepositoryImpl +import de.jeanlucmakiola.calendula.data.prefs.CalendarPrefs +import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import java.nio.file.Path + +/** A Preferences DataStore in [dir]; [scope] pins its writes, e.g. to a test dispatcher. */ +fun testDataStore(dir: Path, name: String, scope: CoroutineScope? = null): DataStore { + val file = { dir.resolve("$name.preferences_pb").toFile() } + return if (scope == null) { + PreferenceDataStoreFactory.create(produceFile = file) + } else { + PreferenceDataStoreFactory.create(scope = scope, produceFile = file) + } +} + +fun testPrefs(dir: Path, scope: CoroutineScope? = null): CalendarPrefs = + CalendarPrefs(testDataStore(dir, "test_prefs", scope)) + +fun testSettings(dir: Path, scope: CoroutineScope? = null): SettingsPrefs = + SettingsPrefs(testDataStore(dir, "test_settings", scope)) + +/** A [CalendarRepositoryImpl] over [source], with fresh prefs in [dir] unless given. */ +fun testRepository( + source: CalendarDataSource, + dir: Path, + io: CoroutineDispatcher, + prefs: CalendarPrefs = testPrefs(dir), + settings: SettingsPrefs = testSettings(dir), +): CalendarRepositoryImpl = CalendarRepositoryImpl(source, prefs, settings, io) + +/** Installs [dispatcher] as `Dispatchers.Main` around each test. */ +@OptIn(ExperimentalCoroutinesApi::class) +class MainDispatcherExtension( + val dispatcher: TestDispatcher = UnconfinedTestDispatcher(), +) : BeforeEachCallback, AfterEachCallback { + override fun beforeEach(context: ExtensionContext) = Dispatchers.setMain(dispatcher) + override fun afterEach(context: ExtensionContext) = Dispatchers.resetMain() +} diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/CalendarRepositoryImplTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/CalendarRepositoryImplTest.kt index 25302a7b..b04a467b 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/CalendarRepositoryImplTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/CalendarRepositoryImplTest.kt @@ -1,18 +1,17 @@ package de.jeanlucmakiola.calendula.data.calendar -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.PreferenceDataStoreFactory -import androidx.datastore.preferences.core.Preferences import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.data.prefs.CalendarPrefs -import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs import de.jeanlucmakiola.calendula.domain.CalendarSource import de.jeanlucmakiola.calendula.domain.EventColorOption import de.jeanlucmakiola.calendula.domain.EventForm import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.FormTime import de.jeanlucmakiola.calendula.domain.SearchCandidate +import de.jeanlucmakiola.calendula.testEvent +import de.jeanlucmakiola.calendula.testPrefs +import de.jeanlucmakiola.calendula.testRepository +import de.jeanlucmakiola.calendula.testSettings import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.datetime.LocalDate @@ -30,21 +29,6 @@ import java.nio.file.Path @OptIn(ExperimentalCoroutinesApi::class) class CalendarRepositoryImplTest { - private fun newPrefs(tempDir: Path): CalendarPrefs = - CalendarPrefs(newDataStore(tempDir)) - - private fun newSettings(tempDir: Path): SettingsPrefs = - SettingsPrefs( - PreferenceDataStoreFactory.create( - produceFile = { tempDir.resolve("repo_test_settings.preferences_pb").toFile() }, - ), - ) - - private fun newDataStore(tempDir: Path): DataStore = - PreferenceDataStoreFactory.create( - produceFile = { tempDir.resolve("repo_test_prefs.preferences_pb").toFile() }, - ) - private fun makeCal( id: Long, name: String = "Cal $id", @@ -56,12 +40,12 @@ class CalendarRepositoryImplTest { id: Long, title: String = "E $id", calendarId: Long = 1L, - ): EventInstance = EventInstance( - instanceId = id, eventId = id, calendarId = calendarId, + ): EventInstance = testEvent( + id = id, calendarId = calendarId, title = title, start = Instant.fromEpochMilliseconds(1_000_000_000L), end = Instant.fromEpochMilliseconds(1_000_003_600L), - isAllDay = false, color = 0xFF000000.toInt(), location = null, + color = 0xFF000000.toInt(), ) @Test @@ -69,7 +53,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { calendarsResult = listOf(makeCal(1L), makeCal(2L)) } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler)) repo.calendars().test { val first = awaitItem() @@ -83,7 +67,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { calendarsResult = listOf(makeCal(1L)) } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler)) repo.calendars().test { assertThat(awaitItem().map { it.id }).containsExactly(1L) @@ -100,7 +84,7 @@ class CalendarRepositoryImplTest { fun `instances re-emit after a write the provider never notified about`(@TempDir tempDir: Path) = runTest { var current = listOf(makeEvent(10L)) val fake = FakeCalendarDataSource().apply { instancesResult = { _, _ -> current } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler)) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.instances(range).test { @@ -121,7 +105,7 @@ class CalendarRepositoryImplTest { instancesResult = { _, _ -> current } writeError = WriteFailedException("boom") } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler)) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.instances(range).test { @@ -146,7 +130,7 @@ class CalendarRepositoryImplTest { listOf(makeEvent(10L)) } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler)) val range = Instant.fromEpochMilliseconds(1_000L)..Instant.fromEpochMilliseconds(2_000L) repo.instances(range).test { @@ -162,7 +146,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { instancesResult = {_, _ -> listOf(makeEvent(10L, "Good")) } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler)) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.instances(range).test { @@ -174,7 +158,7 @@ class CalendarRepositoryImplTest { @Test fun `instances drops events whose calendar the user hid`(@TempDir tempDir: Path) = runTest { - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) prefs.setHiddenCalendarIds(setOf(2L)) val fake = FakeCalendarDataSource().apply { instancesResult = { _, _ -> @@ -184,7 +168,7 @@ class CalendarRepositoryImplTest { ) } } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler), prefs = prefs) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.instances(range).test { @@ -195,7 +179,7 @@ class CalendarRepositoryImplTest { @Test fun `instances re-emits when the hidden set changes`(@TempDir tempDir: Path) = runTest { - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) val fake = FakeCalendarDataSource().apply { instancesResult = { _, _ -> listOf( @@ -204,7 +188,7 @@ class CalendarRepositoryImplTest { ) } } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler), prefs = prefs) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.instances(range).test { @@ -230,7 +214,7 @@ class CalendarRepositoryImplTest { ) } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler)) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.instances(range).test { @@ -243,7 +227,7 @@ class CalendarRepositoryImplTest { fun `instances applies the union of hidden and system-invisible calendars`( @TempDir tempDir: Path, ) = runTest { - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) prefs.setHiddenCalendarIds(setOf(2L)) val fake = FakeCalendarDataSource().apply { calendarsResult = listOf(makeCal(1L), makeCal(2L), makeCal(3L, visible = false)) @@ -255,7 +239,7 @@ class CalendarRepositoryImplTest { ) } } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler), prefs = prefs) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.instances(range).test { @@ -277,7 +261,7 @@ class CalendarRepositoryImplTest { ) } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler)) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.instances(range).test { @@ -302,7 +286,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { calendarsResult = listOf(makeCal(1L), makeCal(2L), makeCal(3L)) } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.setCalendarsVisible(listOf(1L, 3L), false) @@ -316,7 +300,7 @@ class CalendarRepositoryImplTest { // READ granted, WRITE denied: the provider flag can't be written, so the // choice is parked in the pending set — and honoured from there, or the // user's switched-off calendars would come back on upgrade (#75). - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) val fake = FakeCalendarDataSource().apply { canWrite = false calendarsResult = listOf(makeCal(1L), makeCal(2L)) @@ -327,7 +311,7 @@ class CalendarRepositoryImplTest { ) } } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler), prefs = prefs) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.instances(range).test { @@ -346,12 +330,12 @@ class CalendarRepositoryImplTest { fun `calendars reports a pending switch-off as off`(@TempDir tempDir: Path) = runTest { // Otherwise the Settings switch would snap straight back on for a // read-only install, and the pickers would keep offering the calendar. - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) val fake = FakeCalendarDataSource().apply { canWrite = false calendarsResult = listOf(makeCal(1L), makeCal(2L)) } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler), prefs = prefs) repo.calendars().test { assertThat(awaitItem().map { it.isVisibleInSystem }).containsExactly(true, true) @@ -367,13 +351,13 @@ class CalendarRepositoryImplTest { fun `switching a calendar back on without write permission retires its entry`( @TempDir tempDir: Path, ) = runTest { - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) prefs.addPendingDisabledCalendarIds(setOf(2L)) val fake = FakeCalendarDataSource().apply { canWrite = false calendarsResult = listOf(makeCal(1L), makeCal(2L)) } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined, prefs = prefs) repo.setCalendarsVisible(listOf(2L), true) @@ -384,12 +368,12 @@ class CalendarRepositoryImplTest { fun `a provider write clears anything still pending for that calendar`( @TempDir tempDir: Path, ) = runTest { - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) prefs.addPendingDisabledCalendarIds(setOf(2L)) val fake = FakeCalendarDataSource().apply { calendarsResult = listOf(makeCal(1L), makeCal(2L)) } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined, prefs = prefs) repo.setCalendarsVisible(listOf(2L), false) @@ -405,7 +389,7 @@ class CalendarRepositoryImplTest { calendarsResult = listOf(makeCal(1L), makeCal(2L)) instancesResult = { _, _ -> listOf(makeEvent(10L, "A", calendarId = 1L)) } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler)) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) repo.calendars().test { @@ -431,9 +415,9 @@ class CalendarRepositoryImplTest { fun `calendars does not re-emit an unchanged list`(@TempDir tempDir: Path) = runTest { // The store is shared with SettingsPrefs, so an unrelated write would // otherwise re-run every view's combine for an identical list. - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) val fake = FakeCalendarDataSource().apply { calendarsResult = listOf(makeCal(1L)) } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler), prefs = prefs) repo.calendars().test { assertThat(awaitItem().map { it.id }).containsExactly(1L) @@ -453,7 +437,7 @@ class CalendarRepositoryImplTest { // The reconciler's shape: write VISIBLE = 0, then release the id // app-side. The provider's notification arrives afterwards, so the // release must not be read against the pre-write snapshot. - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) prefs.addPendingDisabledCalendarIds(setOf(2L)) val fake = FakeCalendarDataSource().apply { calendarsResult = listOf(makeCal(1L), makeCal(2L)) @@ -461,7 +445,7 @@ class CalendarRepositoryImplTest { listOf(makeEvent(10L, "A", calendarId = 1L), makeEvent(11L, "B", calendarId = 2L)) } } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), UnconfinedTestDispatcher(testScheduler)) + val repo = testRepository(fake, tempDir, UnconfinedTestDispatcher(testScheduler), prefs = prefs) val range = Instant.fromEpochMilliseconds(0)..Instant.fromEpochMilliseconds(10_000L) // Warm the snapshot the way an open view would. @@ -487,7 +471,7 @@ class CalendarRepositoryImplTest { fun `searchEvents drops results from calendars that are off or hidden`( @TempDir tempDir: Path, ) = runTest { - val prefs = newPrefs(tempDir) + val prefs = testPrefs(tempDir) prefs.setHiddenCalendarIds(setOf(3L)) val fake = FakeCalendarDataSource().apply { calendarsResult = listOf(makeCal(1L), makeCal(2L, visible = false), makeCal(3L)) @@ -499,7 +483,7 @@ class CalendarRepositoryImplTest { ) } } - val repo = CalendarRepositoryImpl(fake, prefs, newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined, prefs = prefs) assertThat(repo.searchEvents("e").map { it.event.title }).containsExactly("Shown") } @@ -507,7 +491,7 @@ class CalendarRepositoryImplTest { @Test fun `createEvent delegates and returns the new id`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource().apply { nextInsertId = 77L } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val form = EventForm( calendarId = 1L, title = "Stand-up", @@ -526,9 +510,9 @@ class CalendarRepositoryImplTest { @TempDir tempDir: Path, ) = runTest { val fake = FakeCalendarDataSource() - val settings = newSettings(tempDir) + val settings = testSettings(tempDir) settings.setAllDayReminderTimeMinutes(8 * 60) // 08:00 - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), settings, Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined, settings = settings) val form = EventForm( calendarId = 1L, isAllDay = true, @@ -546,7 +530,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { writeError = WriteFailedException("insert event") } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val form = EventForm( calendarId = 1L, start = FormTime(LocalDateTime(LocalDate(2026, 6, 12), LocalTime(9, 0))), @@ -564,7 +548,7 @@ class CalendarRepositoryImplTest { @Test fun `updateEvent forwards id and both forms`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource() - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val original = EventForm( calendarId = 1L, title = "Stand-up", @@ -581,7 +565,7 @@ class CalendarRepositoryImplTest { @Test fun `moveEvent forwards id, target calendar and both forms`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource().apply { nextInsertId = 77L } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val original = EventForm( calendarId = 1L, title = "Stand-up", @@ -603,7 +587,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { writeError = WriteFailedException("insert moved event into calendar id=3") } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val form = EventForm( calendarId = 1L, start = FormTime(LocalDateTime(LocalDate(2026, 6, 12), LocalTime(9, 0))), @@ -623,7 +607,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { writeError = WriteFailedException("update event id=42") } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val form = EventForm( calendarId = 1L, start = FormTime(LocalDateTime(LocalDate(2026, 6, 12), LocalTime(9, 0))), @@ -641,7 +625,7 @@ class CalendarRepositoryImplTest { @Test fun `deleteEvent delegates to the data source`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource() - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.deleteEvent(eventId = 42L) @@ -652,7 +636,7 @@ class CalendarRepositoryImplTest { @Test fun `deleteOccurrence forwards event id and begin time`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource() - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.deleteOccurrence(eventId = 42L, beginMillis = 1_000L) @@ -663,7 +647,7 @@ class CalendarRepositoryImplTest { @Test fun `deleteEventFromOccurrence forwards event id and begin time`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource() - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.deleteEventFromOccurrence(eventId = 42L, beginMillis = 1_000L) @@ -675,7 +659,7 @@ class CalendarRepositoryImplTest { @Test fun `updateOccurrence forwards target and form, returns the exception id`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource().apply { nextInsertId = 88L } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val form = EventForm( calendarId = 1L, title = "Moved", @@ -698,7 +682,7 @@ class CalendarRepositoryImplTest { @Test fun `updateEventFromOccurrence forwards target and forms, returns the new id`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource().apply { nextInsertId = 99L } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val original = EventForm( calendarId = 1L, title = "Weekly", @@ -725,7 +709,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { writeError = WriteFailedException("delete event id=42") } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) try { repo.deleteEvent(eventId = 42L) @@ -738,7 +722,7 @@ class CalendarRepositoryImplTest { @Test fun `createLocalCalendar delegates and returns the new id`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource().apply { nextCalendarId = 501L } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val id = repo.createLocalCalendar( displayName = "Home", @@ -755,7 +739,7 @@ class CalendarRepositoryImplTest { @Test fun `updateCalendar forwards id, name, color and description`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource() - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.updateCalendar( id = 5L, @@ -772,7 +756,7 @@ class CalendarRepositoryImplTest { @Test fun `deleteCalendar delegates to the data source`(@TempDir tempDir: Path) = runTest { val fake = FakeCalendarDataSource() - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.deleteCalendar(id = 7L) @@ -784,7 +768,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { writeError = WriteFailedException("create local calendar 'Home'") } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) try { repo.createLocalCalendar(displayName = "Home", color = 0, description = null) @@ -799,7 +783,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { eventDetailResult = { null } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) try { repo.eventDetail(eventId = 999L) @@ -818,7 +802,7 @@ class CalendarRepositoryImplTest { if (id == 7L) listOf(EventColorOption("5", 0xFF33B679.toInt())) else emptyList() } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) assertThat(repo.eventColorPalette(7L)) .containsExactly(EventColorOption("5", 0xFF33B679.toInt())) @@ -832,7 +816,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { existingUidsResult = setOf("dup@x") } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val events = listOf( parsedEvent("dup@x"), // already present → skipped parsedEvent("new@x"), // inserted @@ -854,7 +838,7 @@ class CalendarRepositoryImplTest { // A foreign export can hold a row the provider throws on outright; that // must cost the user one event, not the whole file (Codeberg #225). val fake = FakeCalendarDataSource().apply { failingImportSummaries += "bad" } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val events = listOf( parsedEvent("a@x", summary = "good"), parsedEvent("b@x", summary = "bad"), @@ -881,7 +865,7 @@ class CalendarRepositoryImplTest { importsBeforeError = 2 writeError = SecurityException("WRITE_CALENDAR revoked") } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val summary = repo.importEvents( targetCalendarId = 3L, @@ -906,7 +890,7 @@ class CalendarRepositoryImplTest { importsBeforeError = 1 writeError = SecurityException("WRITE_CALENDAR revoked") } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) val summary = repo.importEvents( targetCalendarId = 3L, @@ -927,7 +911,7 @@ class CalendarRepositoryImplTest { val fake = FakeCalendarDataSource().apply { publishedEventColorsResult = { lookups++; emptyList() } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.importEvents(3L, List(5) { parsedEvent("e$it@x") }) @@ -947,7 +931,7 @@ class CalendarRepositoryImplTest { publishedEventColorsResult = { published } eventColorPaletteResult = { emptyList() } } - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.importEvents(3L, listOf(parsedEvent("a@x"))) @@ -959,7 +943,7 @@ class CalendarRepositoryImplTest { @TempDir tempDir: Path, ) = runTest { val fake = FakeCalendarDataSource() - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.exportEvents(calendarIds = setOf(1L, 4L)) @@ -971,7 +955,7 @@ class CalendarRepositoryImplTest { @TempDir tempDir: Path, ) = runTest { val fake = FakeCalendarDataSource() - val repo = CalendarRepositoryImpl(fake, newPrefs(tempDir), newSettings(tempDir), Dispatchers.Unconfined) + val repo = testRepository(fake, tempDir, Dispatchers.Unconfined) repo.exportEvents() diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesSyncEngineTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesSyncEngineTest.kt index 09f9752c..43ffb651 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesSyncEngineTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesSyncEngineTest.kt @@ -1,12 +1,10 @@ package de.jeanlucmakiola.calendula.data.contacts import android.provider.CalendarContract -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.PreferenceDataStoreFactory -import androidx.datastore.preferences.core.Preferences import com.google.common.truth.Truth.assertThat import de.jeanlucmakiola.calendula.data.calendar.FakeCalendarDataSource import de.jeanlucmakiola.calendula.data.calendar.ManagedEventRow +import de.jeanlucmakiola.calendula.testSettings import de.jeanlucmakiola.floret.reminders.ReminderOverride import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs import de.jeanlucmakiola.calendula.domain.contacts.ContactSpecialDate @@ -37,13 +35,6 @@ class SpecialDatesSyncEngineTest { } } - private fun prefs(tempDir: Path): SettingsPrefs { - val store: DataStore = PreferenceDataStoreFactory.create( - produceFile = { tempDir.resolve("settings_test.preferences_pb").toFile() }, - ) - return SettingsPrefs(store) - } - private fun birthday(key: String, name: String, month: Int, day: Int, year: Int?) = ContactSpecialDate(key, name, SpecialDateType.Birthday, month, day, year) @@ -53,7 +44,7 @@ class SpecialDatesSyncEngineTest { calendars: FakeCalendarDataSource = FakeCalendarDataSource(), enabled: Boolean = true, ): Pair { - val settings = prefs(tempDir) + val settings = testSettings(tempDir) if (enabled) settings.setSpecialDatesEnabled(true) return SpecialDatesSyncEngine(contacts, calendars, settings, FakeSpec()) to settings } diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefsTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefsTest.kt index 7c1a9e99..1d99bc74 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefsTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefsTest.kt @@ -1,9 +1,8 @@ package de.jeanlucmakiola.calendula.data.prefs -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.PreferenceDataStoreFactory -import androidx.datastore.preferences.core.Preferences import com.google.common.truth.Truth.assertThat +import de.jeanlucmakiola.calendula.testDataStore +import de.jeanlucmakiola.calendula.testPrefs import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test @@ -12,20 +11,15 @@ import java.nio.file.Path class CalendarPrefsTest { - private fun newDataStore(tempDir: Path): DataStore = - PreferenceDataStoreFactory.create( - produceFile = { tempDir.resolve("test_prefs.preferences_pb").toFile() }, - ) - @Test fun `hiddenCalendarIds defaults to empty when unset`(@TempDir tempDir: Path) = runTest { - val prefs = CalendarPrefs(newDataStore(tempDir)) + val prefs = testPrefs(tempDir) assertThat(prefs.hiddenCalendarIds.first()).isEmpty() } @Test fun `setHiddenCalendarIds round-trips through DataStore`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_prefs") val prefs = CalendarPrefs(store) prefs.setHiddenCalendarIds(setOf(1L, 42L, 7L)) assertThat(prefs.hiddenCalendarIds.first()).isEqualTo(setOf(1L, 42L, 7L)) @@ -33,7 +27,7 @@ class CalendarPrefsTest { @Test fun `setting empty set clears storage`(@TempDir tempDir: Path) = runTest { - val prefs = CalendarPrefs(newDataStore(tempDir)) + val prefs = testPrefs(tempDir) prefs.setHiddenCalendarIds(setOf(1L)) prefs.setHiddenCalendarIds(emptySet()) assertThat(prefs.hiddenCalendarIds.first()).isEmpty() @@ -41,7 +35,7 @@ class CalendarPrefsTest { @Test fun `garbage stored string is parsed defensively`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_prefs") val prefs = CalendarPrefs(store) store.updateData { p -> val mutable = p.toMutablePreferences() @@ -57,7 +51,7 @@ class CalendarPrefsTest { ) = runTest { // Same key as the retired app-local "disabled calendars" model: an // upgrade inherits that set as switch-offs still owed to the provider. - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_prefs") val prefs = CalendarPrefs(store) store.updateData { p -> p.toMutablePreferences().apply { this[CalendarPrefs.DISABLED_IDS_KEY] = "2,9" } @@ -69,13 +63,13 @@ class CalendarPrefsTest { fun `the pending disabled set is empty when nothing was ever stored`( @TempDir tempDir: Path, ) = runTest { - val prefs = CalendarPrefs(newDataStore(tempDir)) + val prefs = testPrefs(tempDir) assertThat(prefs.pendingDisabledCalendarIds.first()).isEmpty() } @Test fun `pending ids are added and dropped one at a time`(@TempDir tempDir: Path) = runTest { - val prefs = CalendarPrefs(newDataStore(tempDir)) + val prefs = testPrefs(tempDir) prefs.addPendingDisabledCalendarIds(listOf(2L, 9L)) prefs.addPendingDisabledCalendarIds(listOf(4L)) @@ -88,7 +82,7 @@ class CalendarPrefsTest { fun `draining the pending set leaves the hidden set alone`( @TempDir tempDir: Path, ) = runTest { - val prefs = CalendarPrefs(newDataStore(tempDir)) + val prefs = testPrefs(tempDir) prefs.setHiddenCalendarIds(setOf(1L)) prefs.addPendingDisabledCalendarIds(setOf(2L)) @@ -104,7 +98,7 @@ class CalendarPrefsTest { ) = runTest { // Null is what makes the evaluation one-shot: "no" is stored just as // firmly as "yes", so the notice can't resurface on a later launch. - val prefs = CalendarPrefs(newDataStore(tempDir)) + val prefs = testPrefs(tempDir) assertThat(prefs.visibilityNoticePending.first()).isNull() prefs.setVisibilityNoticePending(true) diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt index 8502ec1b..ce73b983 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt @@ -1,10 +1,9 @@ package de.jeanlucmakiola.calendula.data.prefs +import de.jeanlucmakiola.calendula.testDataStore +import de.jeanlucmakiola.calendula.testSettings import de.jeanlucmakiola.floret.reminders.ReminderOverride -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.PreferenceDataStoreFactory -import androidx.datastore.preferences.core.Preferences import androidx.compose.ui.unit.dp import com.google.common.truth.Truth.assertThat import de.jeanlucmakiola.calendula.domain.EventFormField @@ -27,14 +26,9 @@ import java.util.Locale class SettingsPrefsTest { - private fun newDataStore(tempDir: Path): DataStore = - PreferenceDataStoreFactory.create( - produceFile = { tempDir.resolve("settings_test.preferences_pb").toFile() }, - ) - @Test fun `defaults are system theme, dynamic colour on, auto week start`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.themeMode.first()).isEqualTo(ThemeMode.SYSTEM) assertThat(prefs.dynamicColor.first()).isTrue() assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.Auto) @@ -42,35 +36,35 @@ class SettingsPrefsTest { @Test fun `theme mode round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setThemeMode(ThemeMode.DARK) assertThat(prefs.themeMode.first()).isEqualTo(ThemeMode.DARK) } @Test fun `black theme mode round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setThemeMode(ThemeMode.BLACK) assertThat(prefs.themeMode.first()).isEqualTo(ThemeMode.BLACK) } @Test fun `dynamic colour round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setDynamicColor(false) assertThat(prefs.dynamicColor.first()).isFalse() } @Test fun `week start round-trips any day`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setWeekStart(WeekStartPref.Day(DayOfWeek.SATURDAY)) assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.Day(DayOfWeek.SATURDAY)) } @Test fun `legacy MONDAY week-start value migrates to Day`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) store.updateData { p -> val m = p.toMutablePreferences() @@ -82,7 +76,7 @@ class SettingsPrefsTest { @Test fun `garbage week-start value falls back to Auto`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) store.updateData { p -> val m = p.toMutablePreferences() @@ -94,7 +88,7 @@ class SettingsPrefsTest { @Test fun `time format defaults to auto and round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.timeFormat.first()).isEqualTo(TimeFormatPref.AUTO) prefs.setTimeFormat(TimeFormatPref.TWELVE_HOUR) assertThat(prefs.timeFormat.first()).isEqualTo(TimeFormatPref.TWELVE_HOUR) @@ -102,7 +96,7 @@ class SettingsPrefsTest { @Test fun `hour lines default off and round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.showHourGrid.first()).isFalse() prefs.setShowHourGrid(true) assertThat(prefs.showHourGrid.first()).isTrue() @@ -112,7 +106,7 @@ class SettingsPrefsTest { fun `timeline scale defaults to regular and round-trips`(@TempDir tempDir: Path) = runTest { // Regular is the historical 56dp scale — an existing install that never // opened the setting must keep the timeline it had (#56). - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.timelineScale.first()).isEqualTo(TimelineScale.Regular) prefs.setTimelineScale(TimelineScale.FitDay) assertThat(prefs.timelineScale.first()).isEqualTo(TimelineScale.FitDay) @@ -122,7 +116,7 @@ class SettingsPrefsTest { fun `a pinched timeline height round-trips`(@TempDir tempDir: Path) = runTest { // The pinch settles on a height between the presets, so the preference // has to store the number, not just a named step (#56). - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) val pinched = TimelineScale.custom(63.5f.dp) prefs.setTimelineScale(pinched) assertThat(prefs.timelineScale.first()).isEqualTo(pinched) @@ -135,7 +129,7 @@ class SettingsPrefsTest { @Test fun `visible hours default off and round-trip`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.timelineHours.first()).isEqualTo(TimelineHours(enabled = false, 7, 22)) prefs.setTimelineHours(TimelineHours(enabled = true, 6, 20)) assertThat(prefs.timelineHours.first()).isEqualTo(TimelineHours(enabled = true, 6, 20)) @@ -146,14 +140,14 @@ class SettingsPrefsTest { @Test fun `visible hours are stored valid`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setTimelineHours(TimelineHours(enabled = true, 23, 22)) assertThat(prefs.timelineHours.first()).isEqualTo(TimelineHours(enabled = true, 20, 22)) } @Test fun `week numbers default off and round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.showWeekNumbers.first()).isFalse() prefs.setShowWeekNumbers(true) assertThat(prefs.showWeekNumbers.first()).isTrue() @@ -161,7 +155,7 @@ class SettingsPrefsTest { @Test fun `autofocus event title defaults on and round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.autofocusEventTitle.first()).isTrue() prefs.setAutofocusEventTitle(false) assertThat(prefs.autofocusEventTitle.first()).isFalse() @@ -169,7 +163,7 @@ class SettingsPrefsTest { @Test fun `agenda ranges default to month and round-trip independently`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.agendaScreenRange.first()).isEqualTo(AgendaRange.Month) assertThat(prefs.agendaWidgetRange.first()).isEqualTo(AgendaRange.Month) @@ -181,7 +175,7 @@ class SettingsPrefsTest { @Test fun `month view style defaults to paged and round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.monthViewStyle.first()).isEqualTo(MonthViewStyle.Paged) prefs.setMonthViewStyle(MonthViewStyle.Split) @@ -193,7 +187,7 @@ class SettingsPrefsTest { @Test fun `garbage stored month view style falls back to paged`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) store.updateData { p -> val m = p.toMutablePreferences() @@ -205,7 +199,7 @@ class SettingsPrefsTest { @Test fun `garbage stored enum falls back to default`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) store.updateData { p -> val m = p.toMutablePreferences() @@ -217,7 +211,7 @@ class SettingsPrefsTest { @Test fun `form fields default to location and description`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.defaultFormFields.first()).containsExactly( EventFormField.Location, EventFormField.Description, @@ -226,7 +220,7 @@ class SettingsPrefsTest { @Test fun `form field toggle round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setFormFieldDefault(EventFormField.Reminders, enabled = true) prefs.setFormFieldDefault(EventFormField.Location, enabled = false) assertThat(prefs.defaultFormFields.first()).containsExactly( @@ -237,14 +231,14 @@ class SettingsPrefsTest { @Test fun `disabling every form field persists as none, not factory default`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) EventFormField.entries.forEach { prefs.setFormFieldDefault(it, enabled = false) } assertThat(prefs.defaultFormFields.first()).isEmpty() } @Test fun `quick-switch defaults to every view enabled in default order`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) val config = prefs.quickSwitchConfig.first() assertThat(config.order).containsExactlyElementsIn(IMPLEMENTED_VIEWS).inOrder() assertThat(config.enabled).containsExactlyElementsIn(IMPLEMENTED_VIEWS) @@ -253,7 +247,7 @@ class SettingsPrefsTest { @Test fun `quick-switch config round-trips order and disabled views`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) val config = QuickSwitchConfig( order = listOf(CalendarView.Agenda, CalendarView.Month, CalendarView.Week, CalendarView.Day), enabled = setOf(CalendarView.Agenda, CalendarView.Month), @@ -270,7 +264,7 @@ class SettingsPrefsTest { @Test fun `quick-switch parse appends missing views enabled and drops unknowns`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) // Only Day (disabled) and Week stored, plus a bogus name; Month & Agenda absent. store.updateData { p -> @@ -292,7 +286,7 @@ class SettingsPrefsTest { @Test fun `updateQuickSwitch transforms the stored value, so intents compose`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) // A toggle then a reorder, each expressed against the *stored* config — // the reorder must not resurrect the just-disabled view (the stale-UI- // snapshot race this API exists to prevent). @@ -311,7 +305,7 @@ class SettingsPrefsTest { @Test fun `drawer order defaults to the implemented order and round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.drawerViewOrder.first()).containsExactlyElementsIn(IMPLEMENTED_VIEWS).inOrder() prefs.setDrawerViewOrder( listOf(CalendarView.Agenda, CalendarView.Day, CalendarView.Week, CalendarView.Month), @@ -324,7 +318,7 @@ class SettingsPrefsTest { @Test fun `drawer order parse appends missing views and drops unknowns`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) store.updateData { p -> val m = p.toMutablePreferences() @@ -339,7 +333,7 @@ class SettingsPrefsTest { @Test fun `unknown stored form-field names are dropped`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) store.updateData { p -> val m = p.toMutablePreferences() @@ -351,28 +345,28 @@ class SettingsPrefsTest { @Test fun `reminders default to enabled, onboarding to not done`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.remindersEnabled.first()).isTrue() assertThat(prefs.reminderOnboardingDone.first()).isFalse() } @Test fun `reminders toggle round-trips`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setRemindersEnabled(false) assertThat(prefs.remindersEnabled.first()).isFalse() } @Test fun `reminder onboarding completes one-way`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setReminderOnboardingDone() assertThat(prefs.reminderOnboardingDone.first()).isTrue() } @Test fun `the wizard arms on a first run`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.armOnboardingWizard() assertThat(prefs.onboardingWizardArmed.first()).isTrue() } @@ -380,7 +374,7 @@ class SettingsPrefsTest { @Test fun `the wizard does not arm once the reminder step is answered`(@TempDir tempDir: Path) = runTest { // An existing install re-granting the permission is not re-onboarded. - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setReminderOnboardingDone() prefs.armOnboardingWizard() assertThat(prefs.onboardingWizardArmed.first()).isFalse() @@ -388,7 +382,7 @@ class SettingsPrefsTest { @Test fun `closing the wizard disarms it`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.armOnboardingWizard() prefs.finishOnboardingWizard() @@ -398,13 +392,13 @@ class SettingsPrefsTest { @Test fun `default reminder is empty until set`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.defaultReminderMinutes.first()).isEmpty() } @Test fun `default reminder round-trips none, single, and multiple lead times`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setDefaultReminderMinutes(listOf(30)) assertThat(prefs.defaultReminderMinutes.first()).containsExactly(30) // Several lead times persist, normalised to distinct ascending order. @@ -416,7 +410,7 @@ class SettingsPrefsTest { @Test fun `legacy single-value default reminder parses as a one-element list`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) // The pre-multi-reminder format stored a bare integer string. store.updateData { p -> @@ -429,7 +423,7 @@ class SettingsPrefsTest { @Test fun `garbage stored default reminder reads as empty`(@TempDir tempDir: Path) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) store.updateData { p -> val m = p.toMutablePreferences() @@ -441,7 +435,7 @@ class SettingsPrefsTest { @Test fun `per-calendar override round-trips minutes, none, and inherit`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.perCalendarReminderOverride.first()).isEmpty() prefs.setCalendarReminderOverride(7L, ReminderOverride.Minutes(listOf(15, 10_080))) @@ -462,7 +456,7 @@ class SettingsPrefsTest { fun `garbage per-calendar override values drop the entry, the none sentinel stays explicit`( @TempDir tempDir: Path, ) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) // Only "none" deliberately means an explicit no-reminder override; a // value with no valid minutes is garbage and must drop its entry so the @@ -480,7 +474,7 @@ class SettingsPrefsTest { @Test fun `all-day default round-trips, including none`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.defaultAllDayReminderMinutes.first()).isEmpty() prefs.setDefaultAllDayReminderMinutes(listOf(1_440)) assertThat(prefs.defaultAllDayReminderMinutes.first()).containsExactly(1_440) @@ -490,7 +484,7 @@ class SettingsPrefsTest { @Test fun `per-calendar all-day override round-trips independently of the timed one`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setCalendarReminderOverride(7L, ReminderOverride.Minutes(listOf(15))) prefs.setCalendarAllDayReminderOverride(7L, ReminderOverride.Minutes(listOf(1_440))) assertThat(prefs.perCalendarReminderOverride.first()).containsExactly(7L, listOf(15)) @@ -527,7 +521,7 @@ class SettingsPrefsTest { @Test fun `all-day reminder time defaults to 9am`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.allDayReminderTimeMinutes.first()).isEqualTo(540) } @@ -535,7 +529,7 @@ class SettingsPrefsTest { fun `all-day reminder time round-trips and clamps to a valid minute-of-day`( @TempDir tempDir: Path, ) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setAllDayReminderTimeMinutes(8 * 60 + 30) assertThat(prefs.allDayReminderTimeMinutes.first()).isEqualTo(510) prefs.setAllDayReminderTimeMinutes(5_000) @@ -546,7 +540,7 @@ class SettingsPrefsTest { @Test fun `snooze duration defaults to 10 minutes`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.snoozeMinutes.first()).isEqualTo(10) } @@ -554,7 +548,7 @@ class SettingsPrefsTest { fun `snooze duration round-trips and clamps to at least one minute`( @TempDir tempDir: Path, ) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setSnoozeMinutes(30) assertThat(prefs.snoozeMinutes.first()).isEqualTo(30) prefs.setSnoozeMinutes(0) @@ -565,7 +559,7 @@ class SettingsPrefsTest { fun `event duration defaults to an hour and clamps to a sane span`( @TempDir tempDir: Path, ) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.defaultEventDurationMinutes.first()).isEqualTo(60) prefs.setDefaultEventDurationMinutes(480) assertThat(prefs.defaultEventDurationMinutes.first()).isEqualTo(480) @@ -579,7 +573,7 @@ class SettingsPrefsTest { fun `per-calendar duration round-trips and clears back to inherit`( @TempDir tempDir: Path, ) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.perCalendarEventDuration.first()).isEmpty() prefs.setCalendarEventDuration(7L, 480) prefs.setCalendarEventDuration(9L, 30) @@ -592,7 +586,7 @@ class SettingsPrefsTest { fun `garbage per-calendar duration entries are dropped, the rest survive`( @TempDir tempDir: Path, ) = runTest { - val store = newDataStore(tempDir) + val store = testDataStore(tempDir, "test_settings") val prefs = SettingsPrefs(store) store.updateData { p -> val m = p.toMutablePreferences() @@ -613,7 +607,7 @@ class SettingsPrefsTest { @Test fun `custom-font stamps default to zero and bump per role independently`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) // Existing installs have no stamp key — that must read as 0. assertThat(prefs.brandFontStamp.first()).isEqualTo(0) assertThat(prefs.plainFontStamp.first()).isEqualTo(0) @@ -640,7 +634,7 @@ class SettingsPrefsTest { @Test fun `special dates feature is off with all types on by default`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.specialDatesEnabled.first()).isFalse() assertThat(prefs.specialDatesTypes.first()).isEqualTo(SpecialDateType.entries.toSet()) assertThat(prefs.specialDatesCalendars.first()).isEmpty() @@ -650,7 +644,7 @@ class SettingsPrefsTest { @Test fun `special date type toggles round-trip`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setSpecialDateTypeEnabled(SpecialDateType.Anniversary, false) assertThat(prefs.specialDatesTypes.first()) .isEqualTo(setOf(SpecialDateType.Birthday, SpecialDateType.Custom)) @@ -658,7 +652,7 @@ class SettingsPrefsTest { @Test fun `managed calendar ids round-trip and drive managedCalendarIds`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) prefs.setSpecialDatesCalendarId(SpecialDateType.Birthday, 7L) prefs.setSpecialDatesCalendarId(SpecialDateType.Anniversary, 9L) assertThat(prefs.specialDatesCalendars.first()) @@ -672,7 +666,7 @@ class SettingsPrefsTest { @Test fun `title template round-trips per type`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.specialDatesTitleTemplates.first()[SpecialDateType.Birthday]).isEmpty() prefs.setSpecialDatesTitleTemplate(SpecialDateType.Birthday, "{name}'s birthday") assertThat(prefs.specialDatesTitleTemplates.first()[SpecialDateType.Birthday]) @@ -681,7 +675,7 @@ class SettingsPrefsTest { @Test fun `sync status records run and stalled reason`(@TempDir tempDir: Path) = runTest { - val prefs = SettingsPrefs(newDataStore(tempDir)) + val prefs = testSettings(tempDir) assertThat(prefs.specialDatesStatus.first()) .isEqualTo(SpecialDatesStatus(lastRun = 0L, stalled = null)) diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventFormTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventFormTest.kt index 2965f4c0..cd9f7d58 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventFormTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventFormTest.kt @@ -1,6 +1,8 @@ package de.jeanlucmakiola.calendula.domain import com.google.common.truth.Truth.assertThat +import de.jeanlucmakiola.calendula.testDetail +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime @@ -120,20 +122,16 @@ class EventFormTest { eventColor: Int? = null, eventColorKey: String? = null, eventTimezone: String? = null, - ): EventDetail = EventDetail( - instance = EventInstance( - instanceId = 1L, - eventId = 1L, + ): EventDetail = testDetail( + instance = testEvent( calendarId = 7L, title = title, start = Instant.fromEpochMilliseconds(rowStart), end = Instant.fromEpochMilliseconds(rowEnd), isAllDay = isAllDay, - color = 0, location = location, ), description = description, - organizer = null, attendees = attendees, rrule = rrule, reminders = reminders, diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventInstanceSpanTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventInstanceSpanTest.kt index 6591a756..a3b4ae25 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventInstanceSpanTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventInstanceSpanTest.kt @@ -1,6 +1,7 @@ package de.jeanlucmakiola.calendula.domain import com.google.common.truth.Truth.assertThat +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone @@ -29,17 +30,8 @@ class EventInstanceSpanTest { private fun utc(y: Int, mo: Int, d: Int, h: Int = 0): Instant = LocalDateTime(y, mo, d, h, 0).toInstant(TimeZone.UTC) - private fun instance(start: Instant, end: Instant, isAllDay: Boolean) = EventInstance( - instanceId = 1L, - eventId = 1L, - calendarId = 1L, - title = "Event", - start = start, - end = end, - isAllDay = isAllDay, - color = 0xFF000000.toInt(), - location = null, - ) + private fun instance(start: Instant, end: Instant, isAllDay: Boolean) = + testEvent(start = start, end = end, isAllDay = isAllDay, color = 0xFF000000.toInt()) @Test fun `all-day event keeps its date west of UTC`() { diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventSearchTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventSearchTest.kt index a6e494ee..818d3efd 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventSearchTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/domain/EventSearchTest.kt @@ -1,6 +1,7 @@ package de.jeanlucmakiola.calendula.domain import com.google.common.truth.Truth.assertThat +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.TimeZone import org.junit.jupiter.api.Test import kotlin.time.Instant @@ -27,15 +28,11 @@ class EventSearchTest { ): SearchCandidate { val begin = Instant.parse(start) return SearchCandidate( - event = EventInstance( - instanceId = nextId, - eventId = nextId++, - calendarId = 1L, + event = testEvent( + id = nextId++, title = title, start = begin, end = begin.plus(kotlin.time.Duration.parse("1h")), - isAllDay = false, - color = 0, location = location, ), description = description, @@ -46,16 +43,12 @@ class EventSearchTest { private fun allDayCandidate(title: String, day: String): SearchCandidate { val begin = Instant.parse("${day}T00:00:00Z") return SearchCandidate( - event = EventInstance( - instanceId = nextId, - eventId = nextId++, - calendarId = 1L, + event = testEvent( + id = nextId++, title = title, start = begin, end = begin.plus(kotlin.time.Duration.parse("24h")), isAllDay = true, - color = 0, - location = null, ), ) } @@ -183,16 +176,12 @@ class EventSearchTest { // Behind UTC an all-day event sits at midnight UTC, which is the previous // month locally (#82) — the month has to come from the span's first day. val newYear = SearchCandidate( - event = EventInstance( - instanceId = 99L, - eventId = 99L, - calendarId = 1L, + event = testEvent( + id = 99L, title = "Test holiday", start = Instant.parse("2027-02-01T00:00:00Z"), end = Instant.parse("2027-02-02T00:00:00Z"), isAllDay = true, - color = 0, - location = null, ), ) diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaItemDatesTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaItemDatesTest.kt index 4e5c7b1b..44a0514c 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaItemDatesTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaItemDatesTest.kt @@ -1,9 +1,8 @@ package de.jeanlucmakiola.calendula.ui.agenda import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.LocalDate -import kotlin.time.Instant import org.junit.jupiter.api.Test /** The day each agenda list item belongs to, for the focus it hands back (#184). */ @@ -12,17 +11,7 @@ class AgendaItemDatesTest { private val monday = LocalDate(2026, 9, 28) private val tuesday = LocalDate(2026, 9, 29) - private fun event(id: Long) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "Event", - start = Instant.fromEpochMilliseconds(0), - end = Instant.fromEpochMilliseconds(0), - isAllDay = false, - color = 0xFF000000.toInt(), - location = null, - ) + private fun event(id: Long) = testEvent(id = id, color = 0xFF000000.toInt()) @Test fun `each day counts its header, events and gap`() { diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaTimeLabelTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaTimeLabelTest.kt index 25c71ed7..97e49427 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaTimeLabelTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaTimeLabelTest.kt @@ -2,6 +2,7 @@ package de.jeanlucmakiola.calendula.ui.agenda import com.google.common.truth.Truth.assertThat import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone @@ -16,17 +17,8 @@ class AgendaTimeLabelTest { private fun at(y: Int, mo: Int, d: Int, h: Int = 0, min: Int = 0): Instant = LocalDateTime(y, mo, d, h, min).toInstant(zone) - private fun event(start: Instant, end: Instant, isAllDay: Boolean = false) = EventInstance( - instanceId = 1, - eventId = 1, - calendarId = 1, - title = "e", - start = start, - end = end, - isAllDay = isAllDay, - color = 0, - location = null, - ) + private fun event(start: Instant, end: Instant, isAllDay: Boolean = false) = + testEvent(title = "e", start = start, end = end, isAllDay = isAllDay) private fun labelOn(y: Int, mo: Int, d: Int, event: EventInstance) = agendaTimeLabel(event, LocalDate(y, mo, d), zone) diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/GroupAgendaDaysTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/GroupAgendaDaysTest.kt index aee5190b..7a847b09 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/GroupAgendaDaysTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/agenda/GroupAgendaDaysTest.kt @@ -3,6 +3,7 @@ package de.jeanlucmakiola.calendula.ui.agenda import com.google.common.truth.Truth.assertThat import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.spansMultipleDays +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone @@ -26,17 +27,7 @@ class GroupAgendaDaysTest { start: Instant, end: Instant, isAllDay: Boolean = false, - ) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1, - title = title, - start = start, - end = end, - isAllDay = isAllDay, - color = 0, - location = null, - ) + ) = testEvent(id = id, title = title, start = start, end = end, isAllDay = isAllDay) private fun days(instances: List) = groupAgendaDays(anchor, windowEnd, instances, zone) diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/IsPastOnTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/IsPastOnTest.kt index f4acea6e..7e003346 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/IsPastOnTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/IsPastOnTest.kt @@ -1,7 +1,7 @@ package de.jeanlucmakiola.calendula.ui.common import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import org.junit.jupiter.api.Test @@ -14,16 +14,11 @@ class IsPastOnTest { private val now = Instant.parse("2026-10-08T12:00:00Z") // All-day Oct 6–9, still running today. - private val summit = EventInstance( - instanceId = 1, - eventId = 1, - calendarId = 1, + private val summit = testEvent( title = "Summit", start = Instant.parse("2026-10-06T00:00:00Z"), end = Instant.parse("2026-10-10T00:00:00Z"), isAllDay = true, - color = 0, - location = null, ) @Test diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/OutOfViewEdgesTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/OutOfViewEdgesTest.kt index 6268b26f..7a4dbe7a 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/OutOfViewEdgesTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/OutOfViewEdgesTest.kt @@ -1,26 +1,15 @@ package de.jeanlucmakiola.calendula.ui.common import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.testEvent import de.jeanlucmakiola.calendula.ui.week.TimedBlock -import kotlin.time.Instant import org.junit.jupiter.api.Test /** Which events a column's edge bars point at, and where a tap scrolls (#188). */ class OutOfViewEdgesTest { private fun block(id: Long, startMin: Int, endMin: Int) = TimedBlock( - event = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "Event $id", - start = Instant.fromEpochMilliseconds(0), - end = Instant.fromEpochMilliseconds(0), - isAllDay = false, - color = 0xFF112233.toInt(), - location = null, - ), + event = testEvent(id = id, title = "Event $id", color = 0xFF112233.toInt()), startMin = startMin, endMin = endMin, lane = 0, diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/RescheduleViewModelTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/RescheduleViewModelTest.kt index 87480ed4..93ef853a 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/RescheduleViewModelTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/RescheduleViewModelTest.kt @@ -1,35 +1,30 @@ package de.jeanlucmakiola.calendula.ui.common -import androidx.datastore.preferences.core.PreferenceDataStoreFactory import com.google.common.truth.Truth.assertThat +import de.jeanlucmakiola.calendula.MainDispatcherExtension import de.jeanlucmakiola.calendula.data.time.FakeZoneSource -import de.jeanlucmakiola.calendula.data.calendar.CalendarRepositoryImpl import de.jeanlucmakiola.calendula.data.calendar.FakeCalendarDataSource -import de.jeanlucmakiola.calendula.data.prefs.CalendarPrefs -import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs import de.jeanlucmakiola.calendula.domain.CalendarSource import de.jeanlucmakiola.calendula.domain.EventDetail -import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.RecurringWriteScope -import kotlinx.coroutines.CoroutineDispatcher +import de.jeanlucmakiola.calendula.testDetail +import de.jeanlucmakiola.calendula.testEvent +import de.jeanlucmakiola.calendula.testPrefs +import de.jeanlucmakiola.calendula.testRepository +import de.jeanlucmakiola.calendula.testSettings import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.test.setMain import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension import org.junit.jupiter.api.io.TempDir import java.nio.file.Path import kotlin.time.Instant @@ -41,10 +36,9 @@ import kotlin.time.Instant @OptIn(ExperimentalCoroutinesApi::class) class RescheduleViewModelTest { - private val dispatcher = UnconfinedTestDispatcher() - - @BeforeEach fun setUp() = Dispatchers.setMain(dispatcher) - @AfterEach fun tearDown() = Dispatchers.resetMain() + @JvmField @RegisterExtension + val mainDispatcher = MainDispatcherExtension() + private val dispatcher = mainDispatcher.dispatcher // Monday 2026-06-08, midday in the device zone — the zone the drop path // reads dates back in. The BYDAY assertions depend on it being a Monday. @@ -69,31 +63,23 @@ class RescheduleViewModelTest { isAllDay: Boolean = false, isException: Boolean = false, anchorMillis: Long = beginMillis, - ): EventDetail = EventDetail( - instance = EventInstance( - instanceId = 42L, eventId = 42L, calendarId = 1L, title = "Standup", + ): EventDetail = testDetail( + instance = testEvent( + id = 42L, title = "Standup", start = Instant.fromEpochMilliseconds(anchorMillis), end = Instant.fromEpochMilliseconds(anchorMillis + (endMillis - beginMillis)), - isAllDay = isAllDay, color = 0xFF000000.toInt(), location = null, + isAllDay = isAllDay, color = 0xFF000000.toInt(), ), - description = null, organizer = null, attendees = emptyList(), rrule = rrule, + rrule = rrule, isException = isException, ) private fun viewModel(tempDir: Path, fake: FakeCalendarDataSource): RescheduleViewModel { - val prefs = CalendarPrefs( - PreferenceDataStoreFactory.create( - scope = CoroutineScope(dispatcher), - produceFile = { tempDir.resolve("move_prefs.preferences_pb").toFile() }, - ), + val repo = testRepository( + fake, tempDir, dispatcher, + prefs = testPrefs(tempDir, CoroutineScope(dispatcher)), + settings = testSettings(tempDir, CoroutineScope(dispatcher)), ) - val settings = SettingsPrefs( - PreferenceDataStoreFactory.create( - scope = CoroutineScope(dispatcher), - produceFile = { tempDir.resolve("move_settings.preferences_pb").toFile() }, - ), - ) - val repo = CalendarRepositoryImpl(fake, prefs, settings, dispatcher as CoroutineDispatcher) return RescheduleViewModel(repo, dispatcher, FakeZoneSource()) } diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/TimedBlockShapeTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/TimedBlockShapeTest.kt index 1e4fc48c..3a78179e 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/TimedBlockShapeTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/TimedBlockShapeTest.kt @@ -1,15 +1,13 @@ package de.jeanlucmakiola.calendula.ui.common import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.timedEvent import de.jeanlucmakiola.calendula.ui.week.MINUTES_PER_DAY import de.jeanlucmakiola.calendula.ui.week.layoutDay import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone -import kotlinx.datetime.atTime import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import androidx.compose.foundation.shape.CornerSize import androidx.compose.ui.unit.dp import org.junit.jupiter.api.Test @@ -25,11 +23,8 @@ class TimedBlockShapeTest { private val day2 = day1.plus(1, DateTimeUnit.DAY) private val day3 = day2.plus(1, DateTimeUnit.DAY) - private fun event(from: LocalDateTimeIsh, to: LocalDateTimeIsh) = EventInstance( - instanceId = 1L, eventId = 1L, calendarId = 1L, title = "E", - start = from.date.atTime(from.hour, 0).toInstant(zone), - end = to.date.atTime(to.hour, 0).toInstant(zone), - isAllDay = false, color = 0xFF112233.toInt(), location = null, + private fun event(from: LocalDateTimeIsh, to: LocalDateTimeIsh) = timedEvent( + from.date, from.hour, to.hour, zone = zone, title = "E", color = 0xFF112233.toInt(), endDate = to.date, ) data class LocalDateTimeIsh(val date: LocalDate, val hour: Int) diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/TimelineDropTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/TimelineDropTest.kt index ff19ac08..83a4c78e 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/TimelineDropTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/TimelineDropTest.kt @@ -1,8 +1,8 @@ package de.jeanlucmakiola.calendula.ui.common import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.repeatedHourOffsetAt +import de.jeanlucmakiola.calendula.timedEvent import de.jeanlucmakiola.calendula.ui.week.TimedBlock import de.jeanlucmakiola.calendula.ui.week.layoutDay import de.jeanlucmakiola.calendula.ui.week.minutesPast @@ -28,16 +28,8 @@ class TimelineDropTest { private val sun = sat.plus(1, DateTimeUnit.DAY) // Sat 5 Sep 20:00 to Sun 6 Sep 08:00. - private val overnight = EventInstance( - instanceId = 1L, - eventId = 1L, - calendarId = 1L, - title = "Night shift", - start = sat.atTime(20, 0).toInstant(zone), - end = sun.atTime(8, 0).toInstant(zone), - isAllDay = false, - color = 0xFF112233.toInt(), - location = null, + private val overnight = timedEvent( + sat, 20, 8, zone = zone, title = "Night shift", color = 0xFF112233.toInt(), endDate = sun, ) private fun blockOn(day: LocalDate): TimedBlock = @@ -131,11 +123,9 @@ class TimelineDropTest { // event's start and the midnight its tail is cut at. val berlin = TimeZone.of("Europe/Berlin") val sun = LocalDate(2026, 3, 29) - val overnightIntoDst = EventInstance( - instanceId = 3L, eventId = 3L, calendarId = 1L, title = "Night shift", - start = LocalDate(2026, 3, 28).atTime(22, 0).toInstant(berlin), - end = sun.atTime(6, 0).toInstant(berlin), - isAllDay = false, color = 0xFF112233.toInt(), location = null, + val overnightIntoDst = timedEvent( + LocalDate(2026, 3, 28), 22, 6, zone = berlin, id = 3L, title = "Night shift", + color = 0xFF112233.toInt(), endDate = sun, ) val tail = layoutDay(listOf(overnightIntoDst), sun, berlin).single() val offset = tail.clipOffsetMinutes(sun, berlin) @@ -148,12 +138,7 @@ class TimelineDropTest { @Test fun `a block that is not clipped is unaffected wherever it sits`() { - val plain = EventInstance( - instanceId = 2L, eventId = 2L, calendarId = 1L, title = "Standup", - start = sat.atTime(9, 0).toInstant(zone), - end = sat.atTime(10, 0).toInstant(zone), - isAllDay = false, color = 0xFF112233.toInt(), location = null, - ) + val plain = timedEvent(sat, 9, zone = zone, id = 2L, title = "Standup", color = 0xFF112233.toInt()) val block = layoutDay(listOf(plain), sat, zone).single() assertThat(block.clipOffsetMinutes(sat, zone)).isEqualTo(0) } diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/ViewMorphKeyTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/ViewMorphKeyTest.kt index b0c5a298..ceb9e0b9 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/ViewMorphKeyTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/ViewMorphKeyTest.kt @@ -1,23 +1,19 @@ package de.jeanlucmakiola.calendula.ui.common import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.LocalDate import org.junit.jupiter.api.Test import kotlin.time.Instant class ViewMorphKeyTest { - private fun instance(instanceId: Long, eventId: Long = 7, startMillis: Long = 1_000L) = EventInstance( - instanceId = instanceId, + private fun instance(instanceId: Long, eventId: Long = 7, startMillis: Long = 1_000L) = testEvent( + id = instanceId, eventId = eventId, - calendarId = 1, title = "Standup", start = Instant.fromEpochMilliseconds(startMillis), end = Instant.fromEpochMilliseconds(startMillis + 3_600_000L), - isAllDay = false, - color = 0, - location = null, ) private val day = LocalDate(2026, 9, 25) diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailViewModelTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailViewModelTest.kt index 2a3ae751..302e71a9 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailViewModelTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailViewModelTest.kt @@ -1,30 +1,24 @@ package de.jeanlucmakiola.calendula.ui.detail import android.content.ContextWrapper -import androidx.datastore.preferences.core.PreferenceDataStoreFactory import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.data.calendar.CalendarRepositoryImpl +import de.jeanlucmakiola.calendula.MainDispatcherExtension import de.jeanlucmakiola.calendula.data.calendar.FakeCalendarDataSource import de.jeanlucmakiola.calendula.data.ics.IcsExporter -import de.jeanlucmakiola.calendula.data.prefs.CalendarPrefs -import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs import de.jeanlucmakiola.calendula.domain.CalendarSource -import de.jeanlucmakiola.calendula.domain.EventDetail -import de.jeanlucmakiola.calendula.domain.EventInstance -import kotlinx.coroutines.CoroutineDispatcher +import de.jeanlucmakiola.calendula.testDetail +import de.jeanlucmakiola.calendula.testEvent +import de.jeanlucmakiola.calendula.testPrefs +import de.jeanlucmakiola.calendula.testRepository +import de.jeanlucmakiola.calendula.testSettings import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.test.setMain -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension import org.junit.jupiter.api.io.TempDir import java.nio.file.Path import kotlin.time.Instant @@ -38,38 +32,29 @@ import kotlin.time.Instant @OptIn(ExperimentalCoroutinesApi::class) class EventDetailViewModelTest { - private val dispatcher = UnconfinedTestDispatcher() - - @BeforeEach fun setUp() = Dispatchers.setMain(dispatcher) - @AfterEach fun tearDown() = Dispatchers.resetMain() + @JvmField @RegisterExtension + val mainDispatcher = MainDispatcherExtension() + private val dispatcher = mainDispatcher.dispatcher private val beginMillis = 1_781_164_800_000L private val endMillis = beginMillis + 3_600_000L - private fun detail(description: String?) = EventDetail( - instance = EventInstance( - instanceId = 42L, eventId = 42L, calendarId = 1L, title = "Standup", + private fun detail(description: String?) = testDetail( + instance = testEvent( + id = 42L, title = "Standup", start = Instant.fromEpochMilliseconds(beginMillis), end = Instant.fromEpochMilliseconds(endMillis), - isAllDay = false, color = 0xFF000000.toInt(), location = null, + color = 0xFF000000.toInt(), ), - description = description, organizer = null, attendees = emptyList(), rrule = null, + description = description, ) private fun viewModel(tempDir: Path, fake: FakeCalendarDataSource): EventDetailViewModel { - val prefs = CalendarPrefs( - PreferenceDataStoreFactory.create( - scope = CoroutineScope(dispatcher), - produceFile = { tempDir.resolve("detail_prefs.preferences_pb").toFile() }, - ), + val repo = testRepository( + fake, tempDir, dispatcher, + prefs = testPrefs(tempDir, CoroutineScope(dispatcher)), + settings = testSettings(tempDir, CoroutineScope(dispatcher)), ) - val settings = SettingsPrefs( - PreferenceDataStoreFactory.create( - scope = CoroutineScope(dispatcher), - produceFile = { tempDir.resolve("detail_settings.preferences_pb").toFile() }, - ), - ) - val repo = CalendarRepositoryImpl(fake, prefs, settings, dispatcher as CoroutineDispatcher) // Only `shareUri()` touches the exporter, and nothing here shares. return EventDetailViewModel(repo, IcsExporter(ContextWrapper(null)), dispatcher) } diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModelTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModelTest.kt index e068c68d..c763b5db 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModelTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModelTest.kt @@ -1,10 +1,8 @@ package de.jeanlucmakiola.calendula.ui.edit import android.content.ContextWrapper -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.PreferenceDataStoreFactory import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.data.calendar.CalendarRepositoryImpl +import de.jeanlucmakiola.calendula.MainDispatcherExtension import de.jeanlucmakiola.calendula.data.calendar.FakeCalendarDataSource import de.jeanlucmakiola.calendula.data.calendar.NoSuchEventException import de.jeanlucmakiola.calendula.data.ics.IcsExporter @@ -14,31 +12,30 @@ import de.jeanlucmakiola.calendula.data.time.FakeZoneSource import de.jeanlucmakiola.calendula.domain.CalendarSource import de.jeanlucmakiola.calendula.domain.EventDetail import de.jeanlucmakiola.calendula.domain.EventForm -import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.FormTime import de.jeanlucmakiola.calendula.domain.startInstant import de.jeanlucmakiola.calendula.domain.problems import de.jeanlucmakiola.calendula.domain.endInstant +import de.jeanlucmakiola.calendula.testDetail +import de.jeanlucmakiola.calendula.testEvent +import de.jeanlucmakiola.calendula.testPrefs +import de.jeanlucmakiola.calendula.testRepository +import de.jeanlucmakiola.calendula.testSettings import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.test.setMain -import kotlinx.coroutines.Dispatchers import de.jeanlucmakiola.calendula.domain.RecurringWriteScope import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension import org.junit.jupiter.api.io.TempDir import java.nio.file.Path import kotlin.time.Instant @@ -51,10 +48,9 @@ import kotlin.time.Instant @OptIn(ExperimentalCoroutinesApi::class) class EventEditViewModelTest { - private val dispatcher = UnconfinedTestDispatcher() - - @BeforeEach fun setUp() = Dispatchers.setMain(dispatcher) - @AfterEach fun tearDown() = Dispatchers.resetMain() + @JvmField @RegisterExtension + val mainDispatcher = MainDispatcherExtension() + private val dispatcher = mainDispatcher.dispatcher private val beginMillis = 1_781_164_800_000L private val endMillis = beginMillis + 3_600_000L @@ -74,32 +70,22 @@ class EventEditViewModelTest { rrule: String? = null, start: Instant = Instant.fromEpochMilliseconds(beginMillis), end: Instant = Instant.fromEpochMilliseconds(endMillis), - ): EventDetail = EventDetail( - instance = EventInstance( - instanceId = 42L, eventId = 42L, calendarId = calendarId, title = "Standup", + ): EventDetail = testDetail( + instance = testEvent( + id = 42L, calendarId = calendarId, title = "Standup", start = start, end = end, - isAllDay = false, color = 0xFF000000.toInt(), location = null, + color = 0xFF000000.toInt(), ), - description = null, organizer = null, attendees = emptyList(), rrule = rrule, + rrule = rrule, ) // Pin the DataStore's scope to the test dispatcher so a settings write (e.g. // last-used calendar) completes under advanceUntilIdle instead of on a real // IO thread the virtual clock can't observe. - private fun prefs(tempDir: Path): CalendarPrefs = CalendarPrefs( - PreferenceDataStoreFactory.create( - scope = CoroutineScope(dispatcher), - produceFile = { tempDir.resolve("vm_prefs.preferences_pb").toFile() }, - ), - ) + private fun prefs(tempDir: Path): CalendarPrefs = testPrefs(tempDir, CoroutineScope(dispatcher)) - private fun settings(tempDir: Path): SettingsPrefs = SettingsPrefs( - PreferenceDataStoreFactory.create( - scope = CoroutineScope(dispatcher), - produceFile = { tempDir.resolve("vm_settings.preferences_pb").toFile() }, - ), - ) + private fun settings(tempDir: Path): SettingsPrefs = testSettings(tempDir, CoroutineScope(dispatcher)) private fun viewModel( tempDir: Path, @@ -113,7 +99,7 @@ class EventEditViewModelTest { io: CoroutineDispatcher = dispatcher, ): EventEditViewModel { val p = prefs(tempDir) - val repo = CalendarRepositoryImpl(fake, p, s, io) + val repo = testRepository(fake, tempDir, io, p, s) // Only `shareUri()` touches the exporter, and nothing here shares. return EventEditViewModel(repo, p, s, IcsExporter(ContextWrapper(null)), dispatcher, zones) } diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ChipAtCellYTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ChipAtCellYTest.kt index 727ad29a..98279b9a 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ChipAtCellYTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ChipAtCellYTest.kt @@ -1,16 +1,14 @@ package de.jeanlucmakiola.calendula.ui.month import com.google.common.truth.Truth.assertThat +import de.jeanlucmakiola.calendula.allDayEvent import de.jeanlucmakiola.calendula.domain.EventInstance -import kotlinx.datetime.DateTimeUnit +import de.jeanlucmakiola.calendula.timedEvent import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate import kotlinx.datetime.Month import kotlinx.datetime.TimeZone import kotlinx.datetime.YearMonth -import kotlinx.datetime.atTime -import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import org.junit.jupiter.api.Test /** @@ -31,29 +29,11 @@ class ChipAtCellYTest { private fun rowOfJuly6(events: List) = layoutMonthWeeks(jul26, DayOfWeek.MONDAY, events, zone)[1] - private fun allDay(from: LocalDate, toInclusive: LocalDate, id: Long) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "A$id", - start = from.atTime(0, 0).toInstant(zone), - end = toInclusive.plus(1, DateTimeUnit.DAY).atTime(0, 0).toInstant(zone), - isAllDay = true, - color = 0xFF2196F3.toInt(), - location = null, - ) + private fun allDay(from: LocalDate, toInclusive: LocalDate, id: Long) = + allDayEvent(from, toInclusive, id = id, title = "A$id", color = 0xFF2196F3.toInt(), zone = zone) - private fun timed(date: LocalDate, hour: Int, id: Long) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "T$id", - start = date.atTime(hour, 0).toInstant(zone), - end = date.atTime(hour + 1, 0).toInstant(zone), - isAllDay = false, - color = 0xFFF44336.toInt(), - location = null, - ) + private fun timed(date: LocalDate, hour: Int, id: Long) = + timedEvent(date, hour, zone = zone, id = id, title = "T$id", color = 0xFFF44336.toInt()) private fun MonthWeek.chipAt(col: Int, cellY: Float) = chipAtCellY( diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ChipSeatTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ChipSeatTest.kt index 710b2adb..fbe7c153 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ChipSeatTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ChipSeatTest.kt @@ -2,12 +2,10 @@ package de.jeanlucmakiola.calendula.ui.month import com.google.common.truth.Truth.assertThat import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.timedEvent import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate -import kotlinx.datetime.atTime import kotlinx.datetime.plus -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toInstant import org.junit.jupiter.api.Test /** @@ -19,17 +17,8 @@ class ChipSeatTest { private val monday = LocalDate(2026, 6, 8) private val days = (0..6).map { monday.plus(it, DateTimeUnit.DAY) } - private fun event(id: Long, title: String = "E") = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = title, - start = monday.atTime(9, 0).toInstant(TimeZone.UTC), - end = monday.atTime(10, 0).toInstant(TimeZone.UTC), - isAllDay = false, - color = 0xFF112233.toInt(), - location = null, - ) + private fun event(id: Long, title: String = "E") = + timedEvent(monday, 9, id = id, title = title, color = 0xFF112233.toInt()) /** * A grid holding [seated] in lane 0 as one bar across [cols], and nothing diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ClipWeekToMonthTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ClipWeekToMonthTest.kt index 3193ded8..365dccc1 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ClipWeekToMonthTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ClipWeekToMonthTest.kt @@ -1,16 +1,14 @@ package de.jeanlucmakiola.calendula.ui.month import com.google.common.truth.Truth.assertThat +import de.jeanlucmakiola.calendula.allDayEvent import de.jeanlucmakiola.calendula.domain.EventInstance -import kotlinx.datetime.DateTimeUnit +import de.jeanlucmakiola.calendula.timedEvent import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate import kotlinx.datetime.Month import kotlinx.datetime.TimeZone import kotlinx.datetime.YearMonth -import kotlinx.datetime.atTime -import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import org.junit.jupiter.api.Test /** @@ -22,29 +20,11 @@ class ClipWeekToMonthTest { private val zone = TimeZone.UTC private val jul26 = YearMonth(2026, Month.JULY) - private fun allDay(from: LocalDate, toInclusive: LocalDate, id: Long = 1L) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "A", - start = from.atTime(0, 0).toInstant(TimeZone.UTC), - end = toInclusive.plus(1, DateTimeUnit.DAY).atTime(0, 0).toInstant(TimeZone.UTC), - isAllDay = true, - color = 0xFF445566.toInt(), - location = null, - ) + private fun allDay(from: LocalDate, toInclusive: LocalDate, id: Long = 1L) = + allDayEvent(from, toInclusive, id = id, title = "A", color = 0xFF445566.toInt()) - private fun timed(date: LocalDate, hour: Int, id: Long = 2L) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "T", - start = date.atTime(hour, 0).toInstant(zone), - end = date.atTime(hour + 1, 0).toInstant(zone), - isAllDay = false, - color = 0xFF112233.toInt(), - location = null, - ) + private fun timed(date: LocalDate, hour: Int, id: Long = 2L) = + timedEvent(date, hour, zone = zone, id = id, title = "T", color = 0xFF112233.toInt()) /** July 2026 starts on a Wednesday, so its first Monday-anchored row is Jun 29 – Jul 5. */ private fun firstRowOfJuly(events: List) = diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/DayIndexTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/DayIndexTest.kt index d71eb198..76e9d862 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/DayIndexTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/DayIndexTest.kt @@ -1,17 +1,17 @@ package de.jeanlucmakiola.calendula.ui.month import com.google.common.truth.Truth.assertThat +import de.jeanlucmakiola.calendula.allDayEvent import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.timedEvent import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate import kotlinx.datetime.Month import kotlinx.datetime.TimeZone import kotlinx.datetime.YearMonth -import kotlinx.datetime.atTime import kotlinx.datetime.minus import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import org.junit.jupiter.api.Test /** @@ -32,32 +32,15 @@ class DayIndexTest { private fun timed(from: LocalDate, startHour: Int, to: LocalDate, endHour: Int): EventInstance { val id = ++nextId - return EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "T$id", - start = from.atTime(startHour, 0).toInstant(zone), - end = to.atTime(endHour, 0).toInstant(zone), - isAllDay = false, - color = 0xFF112233.toInt(), - location = null, + return timedEvent( + from, startHour, endHour, zone = zone, id = id, title = "T$id", + color = 0xFF112233.toInt(), endDate = to, ) } private fun allDay(from: LocalDate, toInclusive: LocalDate): EventInstance { val id = ++nextId - return EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "A$id", - start = from.atTime(0, 0).toInstant(TimeZone.UTC), - end = toInclusive.plus(1, DateTimeUnit.DAY).atTime(0, 0).toInstant(TimeZone.UTC), - isAllDay = true, - color = 0xFF445566.toInt(), - location = null, - ) + return allDayEvent(from, toInclusive, id = id, title = "A$id", color = 0xFF445566.toInt()) } /** Every shape the layout distinguishes, plus the boundary cases around them. */ diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/LaneEventsTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/LaneEventsTest.kt index a64ff01f..63d37e44 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/LaneEventsTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/LaneEventsTest.kt @@ -1,16 +1,14 @@ package de.jeanlucmakiola.calendula.ui.month import com.google.common.truth.Truth.assertThat +import de.jeanlucmakiola.calendula.allDayEvent import de.jeanlucmakiola.calendula.domain.EventInstance -import kotlinx.datetime.DateTimeUnit +import de.jeanlucmakiola.calendula.timedEvent import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate import kotlinx.datetime.Month import kotlinx.datetime.TimeZone import kotlinx.datetime.YearMonth -import kotlinx.datetime.atTime -import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import org.junit.jupiter.api.Test /** @@ -27,29 +25,10 @@ class LaneEventsTest { layoutMonthWeeks(jul26, DayOfWeek.MONDAY, events, zone)[1] private fun allDay(from: LocalDate, toInclusive: LocalDate, id: Long, color: Int = BLUE) = - EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "A$id", - start = from.atTime(0, 0).toInstant(zone), - end = toInclusive.plus(1, DateTimeUnit.DAY).atTime(0, 0).toInstant(zone), - isAllDay = true, - color = color, - location = null, - ) + allDayEvent(from, toInclusive, id = id, title = "A$id", color = color, zone = zone) - private fun timed(date: LocalDate, hour: Int, id: Long, color: Int = RED) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "T$id", - start = date.atTime(hour, 0).toInstant(zone), - end = date.atTime(hour + 1, 0).toInstant(zone), - isAllDay = false, - color = color, - location = null, - ) + private fun timed(date: LocalDate, hour: Int, id: Long, color: Int = RED) = + timedEvent(date, hour, zone = zone, id = id, title = "T$id", color = color) @Test fun `a bar keeps its lane and the day's timed events fill what's left`() { diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthChipTimeTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthChipTimeTest.kt index cf8fd20b..ee272b24 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthChipTimeTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthChipTimeTest.kt @@ -1,14 +1,11 @@ package de.jeanlucmakiola.calendula.ui.month import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance -import kotlinx.datetime.DateTimeUnit +import de.jeanlucmakiola.calendula.allDayEvent +import de.jeanlucmakiola.calendula.timedEvent import kotlinx.datetime.LocalDate import kotlinx.datetime.Month import kotlinx.datetime.TimeZone -import kotlinx.datetime.atTime -import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import org.junit.jupiter.api.Test import java.util.Locale @@ -20,29 +17,10 @@ class MonthChipTimeTest { private val locale = Locale.UK private val day = LocalDate(2026, Month.SEPTEMBER, 2) - private fun timed(hour: Int, minute: Int) = EventInstance( - instanceId = 1L, - eventId = 1L, - calendarId = 1L, - title = "Standup", - start = day.atTime(hour, minute).toInstant(zone), - end = day.atTime(hour + 1, minute).toInstant(zone), - isAllDay = false, - color = 0, - location = null, - ) + private fun timed(hour: Int, minute: Int) = + timedEvent(day, hour, zone = zone, title = "Standup", minute = minute) - private fun allDay() = EventInstance( - instanceId = 2L, - eventId = 2L, - calendarId = 1L, - title = "Holiday", - start = day.atTime(0, 0).toInstant(zone), - end = day.plus(1, DateTimeUnit.DAY).atTime(0, 0).toInstant(zone), - isAllDay = true, - color = 0, - location = null, - ) + private fun allDay() = allDayEvent(day, id = 2L, title = "Holiday", zone = zone) @Test fun `a timed chip shows its start in the 24-hour convention`() { diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLaneCapTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLaneCapTest.kt index 0d7a207f..e9fe2704 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLaneCapTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLaneCapTest.kt @@ -2,14 +2,12 @@ package de.jeanlucmakiola.calendula.ui.month import androidx.compose.ui.unit.dp import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.timedEvent import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate import kotlinx.datetime.Month import kotlinx.datetime.TimeZone import kotlinx.datetime.YearMonth -import kotlinx.datetime.atTime -import kotlinx.datetime.toInstant import org.junit.jupiter.api.Test /** @@ -25,17 +23,8 @@ class MonthLaneCapTest { /** Cell chrome above the band: 6 + 22 + 4. */ private val header = 32.dp - private fun timed(day: LocalDate, hour: Int, id: Long) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "T$id", - start = day.atTime(hour, 0).toInstant(zone), - end = day.atTime(hour + 1, 0).toInstant(zone), - isAllDay = false, - color = 0xFFF44336.toInt(), - location = null, - ) + private fun timed(day: LocalDate, hour: Int, id: Long) = + timedEvent(day, hour, zone = zone, id = id, title = "T$id", color = 0xFFF44336.toInt()) /** Jul 6-12, wholly inside July 2026. */ private fun week(eventsOnMonday: Int) = layoutMonthWeeks( diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLayoutTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLayoutTest.kt index a38c2588..c8a63f10 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLayoutTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLayoutTest.kt @@ -1,8 +1,9 @@ package de.jeanlucmakiola.calendula.ui.month import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.allDayEvent import de.jeanlucmakiola.calendula.domain.EventResponse +import de.jeanlucmakiola.calendula.timedEvent import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate @@ -33,29 +34,12 @@ class MonthLayoutTest { endHour: Int, id: Long = 1L, title: String = "E", - ) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = title, - start = at(date, startHour), - end = at(date, endHour), - isAllDay = false, - color = 0xFF112233.toInt(), - location = null, - ) + ) = timedEvent(date, startHour, endHour, zone = zone, id = id, title = title, color = 0xFF112233.toInt()) /** A timed event running from [date] 20:00 into the next day at [endHour]. */ - private fun overnight(date: LocalDate, endHour: Int = 8) = EventInstance( - instanceId = 7L, - eventId = 7L, - calendarId = 1L, - title = "Night shift", - start = at(date, 20), - end = at(date.plus(1, DateTimeUnit.DAY), endHour), - isAllDay = false, - color = 0xFF112233.toInt(), - location = null, + private fun overnight(date: LocalDate, endHour: Int = 8) = timedEvent( + date, 20, endHour, zone = zone, id = 7L, title = "Night shift", + color = 0xFF112233.toInt(), endDate = date.plus(1, DateTimeUnit.DAY), ) /** All-day events live at UTC midnights with an *exclusive* end. */ @@ -64,17 +48,7 @@ class MonthLayoutTest { toInclusive: LocalDate = from, id: Long = 100L, title: String = "A", - ) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = title, - start = from.atTime(0, 0).toInstant(TimeZone.UTC), - end = toInclusive.plus(1, DateTimeUnit.DAY).atTime(0, 0).toInstant(TimeZone.UTC), - isAllDay = true, - color = 0xFF445566.toInt(), - location = null, - ) + ) = allDayEvent(from, toInclusive, id = id, title = title, color = 0xFF445566.toInt()) @Test fun `startOfGridWeek snaps back to the configured week start`() { diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/search/SearchViewModelTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/search/SearchViewModelTest.kt index f3dff601..8357afa3 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/search/SearchViewModelTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/search/SearchViewModelTest.kt @@ -1,29 +1,23 @@ package de.jeanlucmakiola.calendula.ui.search -import androidx.datastore.preferences.core.PreferenceDataStoreFactory import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.data.calendar.CalendarRepositoryImpl +import de.jeanlucmakiola.calendula.MainDispatcherExtension import de.jeanlucmakiola.calendula.data.calendar.FakeCalendarDataSource -import de.jeanlucmakiola.calendula.data.prefs.CalendarPrefs -import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs import de.jeanlucmakiola.calendula.domain.CalendarSource -import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.RecurringWriteScope import de.jeanlucmakiola.calendula.domain.SearchCandidate -import kotlinx.coroutines.CoroutineDispatcher +import de.jeanlucmakiola.calendula.testEvent +import de.jeanlucmakiola.calendula.testPrefs +import de.jeanlucmakiola.calendula.testRepository +import de.jeanlucmakiola.calendula.testSettings import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.test.setMain -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension import org.junit.jupiter.api.io.TempDir import java.nio.file.Path import kotlin.time.Instant @@ -35,10 +29,9 @@ import kotlin.time.Instant @OptIn(ExperimentalCoroutinesApi::class) class SearchViewModelTest { - private val dispatcher = UnconfinedTestDispatcher() - - @BeforeEach fun setUp() = Dispatchers.setMain(dispatcher) - @AfterEach fun tearDown() = Dispatchers.resetMain() + @JvmField @RegisterExtension + val mainDispatcher = MainDispatcherExtension() + private val dispatcher = mainDispatcher.dispatcher private val begin = 1_800_000_000_000L @@ -53,28 +46,20 @@ class SearchViewModelTest { recurring: Boolean = false, startMillis: Long = begin, ) = SearchCandidate( - EventInstance( - instanceId = id, eventId = id, calendarId = calendarId, title = "Standup $id", + testEvent( + id = id, calendarId = calendarId, title = "Standup $id", start = Instant.fromEpochMilliseconds(startMillis), end = Instant.fromEpochMilliseconds(startMillis + 3_600_000L), - isAllDay = false, color = 0xFF000000.toInt(), location = null, isRecurring = recurring, + color = 0xFF000000.toInt(), isRecurring = recurring, ), ) private fun viewModel(tempDir: Path, fake: FakeCalendarDataSource): SearchViewModel { - val prefs = CalendarPrefs( - PreferenceDataStoreFactory.create( - scope = CoroutineScope(dispatcher), - produceFile = { tempDir.resolve("search_prefs.preferences_pb").toFile() }, - ), + val repo = testRepository( + fake, tempDir, dispatcher, + prefs = testPrefs(tempDir, CoroutineScope(dispatcher)), + settings = testSettings(tempDir, CoroutineScope(dispatcher)), ) - val settings = SettingsPrefs( - PreferenceDataStoreFactory.create( - scope = CoroutineScope(dispatcher), - produceFile = { tempDir.resolve("search_settings.preferences_pb").toFile() }, - ), - ) - val repo = CalendarRepositoryImpl(fake, prefs, settings, dispatcher as CoroutineDispatcher) return SearchViewModel(repo, dispatcher) } diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/ColumnGeometryTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/ColumnGeometryTest.kt index 9dd729c7..24b0ea33 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/ColumnGeometryTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/ColumnGeometryTest.kt @@ -1,11 +1,10 @@ package de.jeanlucmakiola.calendula.ui.week import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate import kotlinx.datetime.plus -import kotlin.time.Instant import org.junit.jupiter.api.Test /** The timeline's columns, standing still and mid view switch (#184). */ @@ -93,15 +92,5 @@ class ColumnGeometryTest { ) } - private fun event(id: Long) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = "Event", - start = Instant.fromEpochMilliseconds(0), - end = Instant.fromEpochMilliseconds(0), - isAllDay = true, - color = 0xFF000000.toInt(), - location = null, - ) + private fun event(id: Long) = testEvent(id = id, isAllDay = true, color = 0xFF000000.toInt()) } diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekLayoutTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekLayoutTest.kt index 6cddb753..c47c8c6c 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekLayoutTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekLayoutTest.kt @@ -1,7 +1,7 @@ package de.jeanlucmakiola.calendula.ui.week import com.google.common.truth.Truth.assertThat -import de.jeanlucmakiola.calendula.domain.EventInstance +import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate @@ -30,16 +30,8 @@ class WeekLayoutTest { allDay: Boolean = false, id: Long = 1L, title: String = "E", - ) = EventInstance( - instanceId = id, - eventId = id, - calendarId = 1L, - title = title, - start = start, - end = end, - isAllDay = allDay, - color = 0xFF112233.toInt(), - location = null, + ) = testEvent( + id = id, title = title, start = start, end = end, isAllDay = allDay, color = 0xFF112233.toInt(), ) @Test