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 <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/408
This commit is contained in:
@@ -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<Attendee> = emptyList(),
|
||||
rrule: String? = null,
|
||||
reminders: List<Reminder> = 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,
|
||||
)
|
||||
@@ -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<Preferences> {
|
||||
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()
|
||||
}
|
||||
+65
-81
@@ -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<Preferences> =
|
||||
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()
|
||||
|
||||
|
||||
+2
-11
@@ -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<Preferences> = 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<SpecialDatesSyncEngine, SettingsPrefs> {
|
||||
val settings = prefs(tempDir)
|
||||
val settings = testSettings(tempDir)
|
||||
if (enabled) settings.setSpecialDatesEnabled(true)
|
||||
return SpecialDatesSyncEngine(contacts, calendars, settings, FakeSpec()) to settings
|
||||
}
|
||||
|
||||
@@ -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<Preferences> =
|
||||
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)
|
||||
|
||||
@@ -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<Preferences> =
|
||||
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))
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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`() {
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -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`() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<EventInstance>) =
|
||||
groupAgendaDays(anchor, windowEnd, instances, zone)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
+19
-33
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+19
-34
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<EventInstance>) =
|
||||
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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<EventInstance>) =
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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`() {
|
||||
|
||||
@@ -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`() {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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`() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user