summaryrefslogtreecommitdiff
path: root/android/app/src/test/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'android/app/src/test/java/org')
-rw-r--r--android/app/src/test/java/org/terst/doot/widget/SlotPackerTest.kt80
-rw-r--r--android/app/src/test/java/org/terst/doot/widget/WidgetRepositoryTest.kt61
2 files changed, 141 insertions, 0 deletions
diff --git a/android/app/src/test/java/org/terst/doot/widget/SlotPackerTest.kt b/android/app/src/test/java/org/terst/doot/widget/SlotPackerTest.kt
new file mode 100644
index 0000000..b6a078c
--- /dev/null
+++ b/android/app/src/test/java/org/terst/doot/widget/SlotPackerTest.kt
@@ -0,0 +1,80 @@
+package org.terst.doot.widget
+
+import org.junit.Assert.*
+import org.junit.Test
+import org.terst.doot.widget.data.*
+import java.time.Instant
+import java.time.temporal.ChronoUnit
+
+class SlotPackerTest {
+
+ private fun makeEvent(startIso: String, endIso: String): WidgetItem = WidgetItem(
+ id = "evt-$startIso", title = "Event", source = "calendar", type = "event",
+ start = startIso, end = endIso
+ )
+
+ private fun makeTask(id: String): WidgetItem = WidgetItem(
+ id = id, title = "Task $id", source = "todoist", type = "task",
+ completable = true
+ )
+
+ private val now = Instant.parse("2026-06-28T10:15:00Z")
+
+ @Test
+ fun `empty tasks returns empty fragments`() {
+ val result = SlotPacker.pack(emptyList(), emptyList(), now)
+ assertTrue(result.isEmpty())
+ }
+
+ @Test
+ fun `single task gets slot starting at now`() {
+ val result = SlotPacker.pack(listOf(makeTask("t1")), emptyList(), now)
+ assertEquals(1, result.size)
+ assertEquals(1, result[0].slots.size)
+ assertEquals(now, result[0].slots[0].slotStart)
+ assertEquals(now.plus(1, ChronoUnit.HOURS), result[0].slots[0].slotEnd)
+ }
+
+ @Test
+ fun `three tasks with no events form one fragment`() {
+ val tasks = listOf(makeTask("t1"), makeTask("t2"), makeTask("t3"))
+ val result = SlotPacker.pack(tasks, emptyList(), now)
+ assertEquals(1, result.size)
+ assertEquals(3, result[0].slots.size)
+ assertEquals(result[0].slots[0].slotEnd, result[0].slots[1].slotStart)
+ assertEquals(result[0].slots[1].slotEnd, result[0].slots[2].slotStart)
+ }
+
+ @Test
+ fun `event between tasks splits into two fragments`() {
+ val tasks = listOf(makeTask("t1"), makeTask("t2"), makeTask("t3"))
+ // Event at 11:15–12:15 falls between slot 1 (10:15–11:15) and slot 2
+ val event = makeEvent("2026-06-28T11:15:00Z", "2026-06-28T12:15:00Z")
+ val result = SlotPacker.pack(tasks, listOf(event), now)
+
+ assertEquals(2, result.size)
+ assertEquals(1, result[0].slots.size) // t1 before event
+ assertEquals(2, result[1].slots.size) // t2, t3 after event
+ }
+
+ @Test
+ fun `task slot jumps past overlapping event`() {
+ val tasks = listOf(makeTask("t1"))
+ // Event occupies 10:00–11:30, overlaps with now (10:15)
+ val event = makeEvent("2026-06-28T10:00:00Z", "2026-06-28T11:30:00Z")
+ val result = SlotPacker.pack(tasks, listOf(event), now)
+
+ assertEquals(1, result.size)
+ val slotStart = result[0].slots[0].slotStart
+ assertEquals(Instant.parse("2026-06-28T11:30:00Z"), slotStart)
+ }
+
+ @Test
+ fun `fragment startTime and endTime match first and last slot`() {
+ val tasks = listOf(makeTask("t1"), makeTask("t2"))
+ val result = SlotPacker.pack(tasks, emptyList(), now)
+ val frag = result[0]
+ assertEquals(frag.slots.first().slotStart, frag.startTime)
+ assertEquals(frag.slots.last().slotEnd, frag.endTime)
+ }
+}
diff --git a/android/app/src/test/java/org/terst/doot/widget/WidgetRepositoryTest.kt b/android/app/src/test/java/org/terst/doot/widget/WidgetRepositoryTest.kt
new file mode 100644
index 0000000..d4c2ce3
--- /dev/null
+++ b/android/app/src/test/java/org/terst/doot/widget/WidgetRepositoryTest.kt
@@ -0,0 +1,61 @@
+package org.terst.doot.widget
+
+import io.mockk.*
+import kotlinx.coroutines.test.runTest
+import okhttp3.*
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.ResponseBody.Companion.toResponseBody
+import org.junit.Assert.*
+import org.junit.Test
+import org.terst.doot.widget.data.WidgetRepository
+
+class WidgetRepositoryTest {
+
+ private val mockClient = mockk<OkHttpClient>()
+ private val mockCall = mockk<Call>()
+
+ private fun responseOf(code: Int, body: String): Response {
+ return Response.Builder()
+ .request(Request.Builder().url("https://example.com").build())
+ .protocol(Protocol.HTTP_1_1)
+ .code(code)
+ .message("")
+ .body(body.toResponseBody("application/json".toMediaType()))
+ .build()
+ }
+
+ @Test
+ fun `fetch returns failure on non-200`() = runTest {
+ every { mockClient.newCall(any()) } returns mockCall
+ every { mockCall.execute() } returns responseOf(401, "unauthorized")
+
+ val repo = WidgetRepository(mockClient, "https://doot.example.com", "token")
+ val result = repo.fetchRaw()
+ assertTrue(result.isFailure)
+ }
+
+ @Test
+ fun `fetch returns success with valid JSON`() = runTest {
+ val json = """{"now":"2026-06-28T10:15:00Z","items":[]}"""
+ every { mockClient.newCall(any()) } returns mockCall
+ every { mockCall.execute() } returns responseOf(200, json)
+
+ val repo = WidgetRepository(mockClient, "https://doot.example.com", "token")
+ val result = repo.fetchRaw()
+ assertTrue(result.isSuccess)
+ assertEquals(0, result.getOrThrow().items.size)
+ }
+
+ @Test
+ fun `fetch sets Authorization header`() = runTest {
+ val json = """{"now":"2026-06-28T10:15:00Z","items":[]}"""
+ val capturedRequest = slot<Request>()
+ every { mockClient.newCall(capture(capturedRequest)) } returns mockCall
+ every { mockCall.execute() } returns responseOf(200, json)
+
+ val repo = WidgetRepository(mockClient, "https://doot.example.com", "mysecret")
+ repo.fetchRaw()
+
+ assertEquals("Bearer mysecret", capturedRequest.captured.header("Authorization"))
+ }
+}