Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.

Commit b8a1a6f

Browse files
authored
[jtx rewrite] Add handler for categories (#416)
* Add CategoriesHandler and tests * Remove test
1 parent de57ca7 commit b8a1a6f

3 files changed

Lines changed: 123 additions & 1 deletion

File tree

lib/src/main/kotlin/at/bitfire/synctools/mapping/jtx/JtxObjectHandler.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package at.bitfire.synctools.mapping.jtx
88

99
import android.content.Entity
1010
import at.bitfire.synctools.icalendar.AssociatedComponents
11+
import at.bitfire.synctools.mapping.jtx.handler.CategoriesHandler
1112
import at.bitfire.synctools.mapping.jtx.handler.DescriptionHandler
1213
import at.bitfire.synctools.mapping.jtx.handler.JtxFieldHandler
1314
import at.bitfire.synctools.storage.jtx.JtxObjectAndExceptions
@@ -29,7 +30,8 @@ class JtxObjectHandler(
2930
private val prodId: ProdId
3031
) {
3132
private val fieldHandlers: Array<JtxFieldHandler> = arrayOf(
32-
DescriptionHandler()
33+
CategoriesHandler(),
34+
DescriptionHandler(),
3335
)
3436

3537
/**
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* This file is part of bitfireAT/synctools which is released under GPLv3.
3+
* Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details.
4+
* SPDX-License-Identifier: GPL-3.0-or-later
5+
*/
6+
7+
package at.bitfire.synctools.mapping.jtx.handler
8+
9+
import android.content.Entity
10+
import at.bitfire.synctools.icalendar.plusAssign
11+
import at.techbee.jtx.JtxContract
12+
import net.fortuna.ical4j.model.TextList
13+
import net.fortuna.ical4j.model.component.CalendarComponent
14+
import net.fortuna.ical4j.model.property.Categories
15+
16+
class CategoriesHandler : JtxFieldHandler {
17+
override fun process(from: Entity, main: Entity, to: CalendarComponent) {
18+
val categoryTexts = from.subValues
19+
.filter { it.uri == JtxContract.JtxCategory.CONTENT_URI }
20+
.mapNotNull { it.values.getAsString(JtxContract.JtxCategory.TEXT) }
21+
22+
if (categoryTexts.isNotEmpty()) {
23+
to += Categories(TextList(categoryTexts))
24+
}
25+
}
26+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* This file is part of bitfireAT/synctools which is released under GPLv3.
3+
* Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details.
4+
* SPDX-License-Identifier: GPL-3.0-or-later
5+
*/
6+
7+
package at.bitfire.synctools.mapping.jtx.handler
8+
9+
import android.content.ContentValues
10+
import android.content.Entity
11+
import androidx.core.content.contentValuesOf
12+
import at.techbee.jtx.JtxContract
13+
import net.fortuna.ical4j.model.Property
14+
import net.fortuna.ical4j.model.component.VToDo
15+
import net.fortuna.ical4j.model.property.Categories
16+
import org.junit.Assert.assertEquals
17+
import org.junit.Assert.assertNull
18+
import org.junit.Test
19+
import org.junit.runner.RunWith
20+
import org.robolectric.RobolectricTestRunner
21+
import kotlin.jvm.optionals.getOrNull
22+
23+
@RunWith(RobolectricTestRunner::class)
24+
class CategoriesHandlerTest {
25+
26+
private val handler = CategoriesHandler()
27+
28+
@Test
29+
fun `No category sub-values produces no CATEGORIES property`() {
30+
val from = Entity(ContentValues())
31+
val output = VToDo()
32+
33+
handler.process(from = from, main = from, to = output)
34+
35+
assertNull(output.getProperty<Categories>(Property.CATEGORIES).getOrNull())
36+
}
37+
38+
@Test
39+
fun `Single category is mapped`() {
40+
val from = Entity(ContentValues()).apply {
41+
addSubValue(JtxContract.JtxCategory.CONTENT_URI, contentValuesOf(JtxContract.JtxCategory.TEXT to "work"))
42+
}
43+
val output = VToDo()
44+
45+
handler.process(from = from, main = from, to = output)
46+
47+
val categories = output.getProperty<Categories>(Property.CATEGORIES).getOrNull()
48+
assertEquals(1, categories?.categories?.texts?.size)
49+
assertEquals("work", categories?.categories?.texts?.first())
50+
}
51+
52+
@Test
53+
fun `Multiple categories are combined into one CATEGORIES property`() {
54+
val from = Entity(ContentValues()).apply {
55+
addSubValue(JtxContract.JtxCategory.CONTENT_URI, contentValuesOf(JtxContract.JtxCategory.TEXT to "work"))
56+
addSubValue(JtxContract.JtxCategory.CONTENT_URI, contentValuesOf(JtxContract.JtxCategory.TEXT to "personal"))
57+
addSubValue(JtxContract.JtxCategory.CONTENT_URI, contentValuesOf(JtxContract.JtxCategory.TEXT to "urgent"))
58+
}
59+
val output = VToDo()
60+
61+
handler.process(from = from, main = from, to = output)
62+
63+
val categories = output.getProperty<Categories>(Property.CATEGORIES).getOrNull()
64+
assertEquals(3, categories?.categories?.texts?.size)
65+
assertEquals(setOf("work", "personal", "urgent"), categories?.categories?.texts?.toSet())
66+
}
67+
68+
@Test
69+
fun `Category sub-value with null TEXT is skipped`() {
70+
val from = Entity(ContentValues()).apply {
71+
addSubValue(JtxContract.JtxCategory.CONTENT_URI, contentValuesOf(JtxContract.JtxCategory.TEXT to "work"))
72+
addSubValue(JtxContract.JtxCategory.CONTENT_URI, ContentValues()) // no TEXT
73+
}
74+
val output = VToDo()
75+
76+
handler.process(from = from, main = from, to = output)
77+
78+
val categories = output.getProperty<Categories>(Property.CATEGORIES).getOrNull()
79+
assertEquals(1, categories?.categories?.texts?.size)
80+
assertEquals("work", categories?.categories?.texts?.first())
81+
}
82+
83+
@Test
84+
fun `Sub-values with other URIs are ignored`() {
85+
val from = Entity(ContentValues()).apply {
86+
addSubValue(JtxContract.JtxAlarm.CONTENT_URI, contentValuesOf(JtxContract.JtxCategory.TEXT to "should-be-ignored"))
87+
}
88+
val output = VToDo()
89+
90+
handler.process(from = from, main = from, to = output)
91+
92+
assertNull(output.getProperty<Categories>(Property.CATEGORIES).getOrNull())
93+
}
94+
}

0 commit comments

Comments
 (0)