fix(edit): correct the recurrence picker's read-out and end handling
Review follow-ups to the picker redesign, plus the bugs it exposed. The live read-out now renders customResult itself — the exact string OK would save — instead of rebuilding a parallel rule with `interval ?: 1` and `end ?: Never` fallbacks, which let it confidently describe a rule that differed from the selected controls whenever a field was invalid. Shrink the invalid space behind it: a blank amount field reads as its visible placeholder (1 / 10) rather than as an error, and backing out of the date picker falls back to "never" instead of stranding a dateless "on a date". Only an out-of-range 0 remains invalid, and that now says so rather than greying out OK with no cause. UNTIL displayed the day after the one picked for zones behind UTC: toRRule deliberately writes the end of the chosen *local* day expressed in UTC (the provider applies UNTIL coarsely), so the read side must convert back before taking the date. Fixes the detail screen too, and untilLocalDate is extracted so it can be tested. Also: hoist a remember() out of a conditional (a slot that appears and disappears breaks positional memoisation), match GroupedSurface's 22dp corners instead of a drifted local 20dp copy, move the cards onto GroupedSurface, drop the segmented row's icon slot so longer unit labels fit, and reserve two lines so the stack stops shifting as the phrase grows with each weekday. Extract SettingsPrefs.firstDayOfWeek(scope), replacing three copies that had drifted onto different initialValues. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* The read side of the UNTIL round-trip. `SimpleRecurrence.toRRule` deliberately
|
||||
* writes the end of the chosen *local* day expressed in UTC (see its docs — the
|
||||
* provider applies UNTIL coarsely), so displaying it means converting back into
|
||||
* the device zone first. Reading the leading digits raw showed the wrong day for
|
||||
* zones behind UTC.
|
||||
*/
|
||||
class RecurrenceTextTest {
|
||||
|
||||
private val berlin = ZoneId.of("Europe/Berlin")
|
||||
private val losAngeles = ZoneId.of("America/Los_Angeles")
|
||||
private val utc = ZoneId.of("UTC")
|
||||
|
||||
@Test
|
||||
fun `UTC form converts back to the picked day west of UTC`() {
|
||||
// toRRule("2026-12-31", America/Los_Angeles) → 23:59:59 local = 07:59:59Z
|
||||
// on 1 Jan. Reading the digits raw would show 1 Jan 2027.
|
||||
assertThat(untilLocalDate("20270101T075959Z", losAngeles))
|
||||
.isEqualTo(LocalDate.of(2026, 12, 31))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UTC form converts back to the picked day east of UTC`() {
|
||||
// Berlin in December is UTC+1: 23:59:59 local = 22:59:59Z the same day.
|
||||
assertThat(untilLocalDate("20261231T225959Z", berlin))
|
||||
.isEqualTo(LocalDate.of(2026, 12, 31))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UTC form is unchanged at UTC itself`() {
|
||||
assertThat(untilLocalDate("20261231T235959Z", utc))
|
||||
.isEqualTo(LocalDate.of(2026, 12, 31))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `summer offset is honoured, not a fixed one`() {
|
||||
// Berlin in July is UTC+2, so the same local end-of-day lands at 21:59:59Z.
|
||||
assertThat(untilLocalDate("20260801T215959Z", berlin))
|
||||
.isEqualTo(LocalDate.of(2026, 8, 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `date-only form is already local and passes through`() {
|
||||
assertThat(untilLocalDate("20261231", losAngeles))
|
||||
.isEqualTo(LocalDate.of(2026, 12, 31))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `floating date-time form uses its date as-is`() {
|
||||
// No trailing Z, so it isn't an instant — no conversion may be applied.
|
||||
assertThat(untilLocalDate("20261231T235959", losAngeles))
|
||||
.isEqualTo(LocalDate.of(2026, 12, 31))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `garbage returns null rather than throwing`() {
|
||||
assertThat(untilLocalDate("", berlin)).isNull()
|
||||
assertThat(untilLocalDate("nonsense", berlin)).isNull()
|
||||
assertThat(untilLocalDate("2026", berlin)).isNull()
|
||||
assertThat(untilLocalDate("20261340", berlin)).isNull()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user