summaryrefslogtreecommitdiff
path: root/android/app/src/test/java/org/terst/doot/widget/ui/DootWidgetGridTest.kt
blob: 550c53322f037a748c6d2adfaa96ae9b084f8852 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package org.terst.doot.widget.ui

import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.After
import org.junit.Test
import org.terst.doot.widget.data.WidgetItem
import java.time.Instant
import java.util.TimeZone

class DootWidgetGridTest {

    private lateinit var originalTimeZone: TimeZone

    @Before
    fun setUp() {
        originalTimeZone = TimeZone.getDefault()
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
    }

    @After
    fun tearDown() {
        TimeZone.setDefault(originalTimeZone)
    }

    private fun tomorrowEvent(startIso: String, endIso: String): WidgetItem = WidgetItem(
        id = "tmrw", title = "Tomorrow Event", source = "calendar", type = "event",
        start = startIso, end = endIso
    )

    @Test
    fun `calcGridStart on an unfiltered mixed list is skewed by tomorrow's event hour`() {
        // Documents why WidgetRoot MUST pre-filter to today-only events before calling
        // this function: it has no date awareness, only hour-of-day. "now" is 22:00
        // today (2026-07-12); the only item is tomorrow's (2026-07-13) 10:00 event.
        val mixedList = listOf(tomorrowEvent("2026-07-13T10:00:00Z", "2026-07-13T14:30:00Z"))
        assertEquals(9, calcGridStart(mixedList, nowHour = 22))
    }

    @Test
    fun `calcGridStart on the correctly today-filtered (empty) list falls back to nowHour`() {
        val gridStart = calcGridStart(emptyList(), nowHour = 22)
        assertEquals(21, gridStart)
    }

    @Test
    fun `calcGridEnd falls back to nowHour plus eight when no today events, capped at 23`() {
        val gridEnd = calcGridEnd(emptyList(), nowHour = 22)
        assertEquals(23, gridEnd)
    }

    // isPastEvent: 2026-08-05 change pulling already-ended events out of the grid to
    // stack above it as list rows, same treatment past tasks already got.

    private fun event(endIso: String?) = WidgetItem(
        id = "e", title = "Event", source = "calendar", type = "event",
        start = "2026-07-12T09:00:00Z", end = endIso
    )

    private fun task(startIso: String) = WidgetItem(
        id = "t", title = "Task", source = "doot", type = "task", start = startIso
    )

    @Test
    fun `isPastEvent is true once end has passed`() {
        val now = Instant.parse("2026-07-12T10:00:00Z")
        assertTrue(isPastEvent(event(endIso = "2026-07-12T09:30:00Z"), now))
    }

    @Test
    fun `isPastEvent is false while still ongoing`() {
        val now = Instant.parse("2026-07-12T10:00:00Z")
        assertFalse(isPastEvent(event(endIso = "2026-07-12T10:30:00Z"), now))
    }

    @Test
    fun `isPastEvent is false with no end time -- never considered past`() {
        val now = Instant.parse("2026-07-12T23:00:00Z")
        assertFalse(isPastEvent(event(endIso = null), now))
    }

    @Test
    fun `isPastEvent is always false for tasks -- SlotPacker already handles those`() {
        val now = Instant.parse("2026-07-12T10:00:00Z")
        assertFalse(isPastEvent(task(startIso = "2026-07-12T08:00:00Z"), now))
    }

    // moonPhaseEmoji: values verified against direct computation before writing these
    // assertions (days-since-anchor / synodic-month arithmetic), not hand-computed.

    @Test
    fun `moonPhaseEmoji on the reference new moon date is new moon`() {
        assertEquals("🌑", moonPhaseEmoji(java.time.LocalDate.of(2000, 1, 6)))
    }

    @Test
    fun `moonPhaseEmoji about half a synodic month later is full moon`() {
        assertEquals("🌕", moonPhaseEmoji(java.time.LocalDate.of(2000, 1, 21)))
    }

    @Test
    fun `moonPhaseEmoji never indexes out of bounds across a full cycle`() {
        val anchor = java.time.LocalDate.of(2000, 1, 6)
        for (offset in 0..365) {
            // Must not throw for any date in a full year -- the real assertion here is
            // "doesn't crash," the emoji values themselves are checked in the two tests above.
            moonPhaseEmoji(anchor.plusDays(offset.toLong()))
        }
    }
}