From 11aabb3d918473e638ab0af077495791724c8681 Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Fri, 22 May 2026 13:25:21 +0200 Subject: [PATCH 1/6] Implement PriorityHandler with unit test --- .../mapping/tasks/DmfsTaskProcessor.kt | 4 +- .../mapping/tasks/handler/PriorityHandler.kt | 19 ++++++ .../tasks/handler/PriorityHandlerTest.kt | 59 +++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandler.kt create mode 100644 lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandlerTest.kt diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt index 67a4c220..93c0e678 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt @@ -13,6 +13,7 @@ import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import at.bitfire.synctools.mapping.tasks.handler.AlarmsHandler import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskFieldHandler import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskPropertyHandler +import at.bitfire.synctools.mapping.tasks.handler.PriorityHandler import at.bitfire.synctools.mapping.tasks.handler.SequenceHandler import at.bitfire.synctools.mapping.tasks.handler.TitleHandler import at.bitfire.synctools.mapping.tasks.handler.UidHandler @@ -57,6 +58,7 @@ class DmfsTaskProcessor( UidHandler(), TitleHandler(), SequenceHandler(), + PriorityHandler(), ) private val propertyHandlers: Map = mapOf( @@ -94,8 +96,6 @@ class DmfsTaskProcessor( } } - values.getAsInteger(Tasks.PRIORITY)?.let { to.priority = it } - // Note: big method – maybe split? Depends on how we want to proceed with refactoring. to.classification = when (values.getAsInteger(Tasks.CLASSIFICATION)) { diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandler.kt new file mode 100644 index 00000000..68905cc2 --- /dev/null +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandler.kt @@ -0,0 +1,19 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import at.bitfire.ical4android.Task +import org.dmfs.tasks.contract.TaskContract.Tasks + +class PriorityHandler : DmfsTaskFieldHandler { + + override fun process(from: ContentValues, to: Task) { + from.getAsInteger(Tasks.PRIORITY)?.let { to.priority = it } + } + +} diff --git a/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandlerTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandlerTest.kt new file mode 100644 index 00000000..3daf9fd7 --- /dev/null +++ b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandlerTest.kt @@ -0,0 +1,59 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import androidx.core.content.contentValuesOf +import at.bitfire.ical4android.Task +import org.dmfs.tasks.contract.TaskContract.Tasks +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class PriorityHandlerTest { + + private val handler = PriorityHandler() + + @Test + fun `No PRIORITY`() { + val task = Task() + handler.process(ContentValues(), task) + assertNull(task.priority) + } + + @Test + fun `PRIORITY is 0 (undefined)`() { + val task = Task() + handler.process(contentValuesOf(Tasks.PRIORITY to 0), task) + assertEquals(0, task.priority) + } + + @Test + fun `PRIORITY is 1 (high)`() { + val task = Task() + handler.process(contentValuesOf(Tasks.PRIORITY to 1), task) + assertEquals(1, task.priority) + } + + @Test + fun `PRIORITY is 5 (medium)`() { + val task = Task() + handler.process(contentValuesOf(Tasks.PRIORITY to 5), task) + assertEquals(5, task.priority) + } + + @Test + fun `PRIORITY is 9 (low)`() { + val task = Task() + handler.process(contentValuesOf(Tasks.PRIORITY to 9), task) + assertEquals(9, task.priority) + } + +} From c048d6456f53399b92bac8d48d12eb8420a8cefd Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Fri, 22 May 2026 13:26:03 +0200 Subject: [PATCH 2/6] Implement ClassificationHandler with unit test --- .../mapping/tasks/DmfsTaskProcessor.kt | 10 +--- .../tasks/handler/ClassificationHandler.kt | 25 ++++++++ .../handler/ClassificationHandlerTest.kt | 60 +++++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/ClassificationHandler.kt create mode 100644 lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/ClassificationHandlerTest.kt diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt index 93c0e678..8a79cb54 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt @@ -11,6 +11,7 @@ import at.bitfire.ical4android.Task import at.bitfire.ical4android.UnknownProperty import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import at.bitfire.synctools.mapping.tasks.handler.AlarmsHandler +import at.bitfire.synctools.mapping.tasks.handler.ClassificationHandler import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskFieldHandler import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskPropertyHandler import at.bitfire.synctools.mapping.tasks.handler.PriorityHandler @@ -22,7 +23,6 @@ import at.bitfire.synctools.storage.tasks.DmfsTaskList import at.bitfire.synctools.util.AndroidTimeUtils import net.fortuna.ical4j.model.TimeZoneRegistryFactory import net.fortuna.ical4j.model.parameter.RelType -import net.fortuna.ical4j.model.property.Clazz import net.fortuna.ical4j.model.property.Completed import net.fortuna.ical4j.model.property.DtStart import net.fortuna.ical4j.model.property.Due @@ -59,6 +59,7 @@ class DmfsTaskProcessor( TitleHandler(), SequenceHandler(), PriorityHandler(), + ClassificationHandler(), ) private val propertyHandlers: Map = mapOf( @@ -98,13 +99,6 @@ class DmfsTaskProcessor( // Note: big method – maybe split? Depends on how we want to proceed with refactoring. - to.classification = when (values.getAsInteger(Tasks.CLASSIFICATION)) { - Tasks.CLASSIFICATION_PUBLIC -> Clazz(Clazz.VALUE_PUBLIC) - Tasks.CLASSIFICATION_PRIVATE -> Clazz(Clazz.VALUE_PRIVATE) - Tasks.CLASSIFICATION_CONFIDENTIAL -> Clazz(Clazz.VALUE_CONFIDENTIAL) - else -> null - } - values.getAsLong(Tasks.COMPLETED)?.let { to.completedAt = Completed(Instant.ofEpochMilli(it)) } values.getAsInteger(Tasks.PERCENT_COMPLETE)?.let { to.percentComplete = it } diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/ClassificationHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/ClassificationHandler.kt new file mode 100644 index 00000000..a9c6c720 --- /dev/null +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/ClassificationHandler.kt @@ -0,0 +1,25 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import at.bitfire.ical4android.Task +import net.fortuna.ical4j.model.property.Clazz +import org.dmfs.tasks.contract.TaskContract.Tasks + +class ClassificationHandler : DmfsTaskFieldHandler { + + override fun process(from: ContentValues, to: Task) { + to.classification = when (from.getAsInteger(Tasks.CLASSIFICATION)) { + Tasks.CLASSIFICATION_PUBLIC -> Clazz(Clazz.VALUE_PUBLIC) + Tasks.CLASSIFICATION_PRIVATE -> Clazz(Clazz.VALUE_PRIVATE) + Tasks.CLASSIFICATION_CONFIDENTIAL -> Clazz(Clazz.VALUE_CONFIDENTIAL) + else -> null + } + } + +} diff --git a/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/ClassificationHandlerTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/ClassificationHandlerTest.kt new file mode 100644 index 00000000..83fc4f0a --- /dev/null +++ b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/ClassificationHandlerTest.kt @@ -0,0 +1,60 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import androidx.core.content.contentValuesOf +import at.bitfire.ical4android.Task +import net.fortuna.ical4j.model.property.Clazz +import org.dmfs.tasks.contract.TaskContract.Tasks +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class ClassificationHandlerTest { + + private val handler = ClassificationHandler() + + @Test + fun `No CLASSIFICATION`() { + val task = Task() + handler.process(ContentValues(), task) + assertNull(task.classification) + } + + @Test + fun `CLASSIFICATION_PUBLIC maps to CLASS PUBLIC`() { + val task = Task() + handler.process(contentValuesOf(Tasks.CLASSIFICATION to Tasks.CLASSIFICATION_PUBLIC), task) + assertEquals(Clazz(Clazz.VALUE_PUBLIC), task.classification) + } + + @Test + fun `CLASSIFICATION_PRIVATE maps to CLASS PRIVATE`() { + val task = Task() + handler.process(contentValuesOf(Tasks.CLASSIFICATION to Tasks.CLASSIFICATION_PRIVATE), task) + assertEquals(Clazz(Clazz.VALUE_PRIVATE), task.classification) + } + + @Test + fun `CLASSIFICATION_CONFIDENTIAL maps to CLASS CONFIDENTIAL`() { + val task = Task() + handler.process(contentValuesOf(Tasks.CLASSIFICATION to Tasks.CLASSIFICATION_CONFIDENTIAL), task) + assertEquals(Clazz(Clazz.VALUE_CONFIDENTIAL), task.classification) + } + + @Test + fun `Unknown CLASSIFICATION maps to null`() { + val task = Task() + handler.process(contentValuesOf(Tasks.CLASSIFICATION to 99), task) + assertNull(task.classification) + } + +} From e363bfa31fff97c4db2c2b3ca21c12350d6017cf Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Fri, 22 May 2026 13:26:46 +0200 Subject: [PATCH 3/6] Implement StatusHandler with unit test --- .../mapping/tasks/DmfsTaskProcessor.kt | 10 +-- .../mapping/tasks/handler/StatusHandler.kt | 25 +++++++ .../tasks/handler/StatusHandlerTest.kt | 66 +++++++++++++++++++ 3 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/StatusHandler.kt create mode 100644 lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/StatusHandlerTest.kt diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt index 8a79cb54..f6abb3ec 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt @@ -16,6 +16,7 @@ import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskFieldHandler import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskPropertyHandler import at.bitfire.synctools.mapping.tasks.handler.PriorityHandler import at.bitfire.synctools.mapping.tasks.handler.SequenceHandler +import at.bitfire.synctools.mapping.tasks.handler.StatusHandler import at.bitfire.synctools.mapping.tasks.handler.TitleHandler import at.bitfire.synctools.mapping.tasks.handler.UidHandler import at.bitfire.synctools.storage.tasks.DmfsTask.Companion.UNKNOWN_PROPERTY_DATA @@ -33,7 +34,6 @@ import net.fortuna.ical4j.model.property.Organizer import net.fortuna.ical4j.model.property.RDate import net.fortuna.ical4j.model.property.RRule import net.fortuna.ical4j.model.property.RelatedTo -import net.fortuna.ical4j.model.property.Status import org.dmfs.tasks.contract.TaskContract.Properties import org.dmfs.tasks.contract.TaskContract.Property.Alarm import org.dmfs.tasks.contract.TaskContract.Property.Category @@ -60,6 +60,7 @@ class DmfsTaskProcessor( SequenceHandler(), PriorityHandler(), ClassificationHandler(), + StatusHandler(), ) private val propertyHandlers: Map = mapOf( @@ -102,13 +103,6 @@ class DmfsTaskProcessor( values.getAsLong(Tasks.COMPLETED)?.let { to.completedAt = Completed(Instant.ofEpochMilli(it)) } values.getAsInteger(Tasks.PERCENT_COMPLETE)?.let { to.percentComplete = it } - to.status = when (values.getAsInteger(Tasks.STATUS)) { - Tasks.STATUS_IN_PROCESS -> Status(Status.VALUE_IN_PROCESS) - Tasks.STATUS_COMPLETED -> Status(Status.VALUE_COMPLETED) - Tasks.STATUS_CANCELLED -> Status(Status.VALUE_CANCELLED) - else -> Status(Status.VALUE_NEEDS_ACTION) - } - val allDay = (values.getAsInteger(Tasks.IS_ALLDAY) ?: 0) != 0 val tzID = values.getAsString(Tasks.TZ) diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/StatusHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/StatusHandler.kt new file mode 100644 index 00000000..6df65f5e --- /dev/null +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/StatusHandler.kt @@ -0,0 +1,25 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import at.bitfire.ical4android.Task +import net.fortuna.ical4j.model.property.Status +import org.dmfs.tasks.contract.TaskContract.Tasks + +class StatusHandler : DmfsTaskFieldHandler { + + override fun process(from: ContentValues, to: Task) { + to.status = when (from.getAsInteger(Tasks.STATUS)) { + Tasks.STATUS_IN_PROCESS -> Status(Status.VALUE_IN_PROCESS) + Tasks.STATUS_COMPLETED -> Status(Status.VALUE_COMPLETED) + Tasks.STATUS_CANCELLED -> Status(Status.VALUE_CANCELLED) + else -> Status(Status.VALUE_NEEDS_ACTION) + } + } + +} diff --git a/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/StatusHandlerTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/StatusHandlerTest.kt new file mode 100644 index 00000000..b52eae17 --- /dev/null +++ b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/StatusHandlerTest.kt @@ -0,0 +1,66 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import androidx.core.content.contentValuesOf +import at.bitfire.ical4android.Task +import net.fortuna.ical4j.model.property.Status +import org.dmfs.tasks.contract.TaskContract.Tasks +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class StatusHandlerTest { + + private val handler = StatusHandler() + + @Test + fun `No STATUS defaults to NEEDS-ACTION`() { + val task = Task() + handler.process(ContentValues(), task) + assertEquals(Status(Status.VALUE_NEEDS_ACTION), task.status) + } + + @Test + fun `STATUS_NEEDS_ACTION maps to NEEDS-ACTION`() { + val task = Task() + handler.process(contentValuesOf(Tasks.STATUS to Tasks.STATUS_NEEDS_ACTION), task) + assertEquals(Status(Status.VALUE_NEEDS_ACTION), task.status) + } + + @Test + fun `STATUS_IN_PROCESS maps to IN-PROCESS`() { + val task = Task() + handler.process(contentValuesOf(Tasks.STATUS to Tasks.STATUS_IN_PROCESS), task) + assertEquals(Status(Status.VALUE_IN_PROCESS), task.status) + } + + @Test + fun `STATUS_COMPLETED maps to COMPLETED`() { + val task = Task() + handler.process(contentValuesOf(Tasks.STATUS to Tasks.STATUS_COMPLETED), task) + assertEquals(Status(Status.VALUE_COMPLETED), task.status) + } + + @Test + fun `STATUS_CANCELLED maps to CANCELLED`() { + val task = Task() + handler.process(contentValuesOf(Tasks.STATUS to Tasks.STATUS_CANCELLED), task) + assertEquals(Status(Status.VALUE_CANCELLED), task.status) + } + + @Test + fun `Unknown STATUS defaults to NEEDS-ACTION`() { + val task = Task() + handler.process(contentValuesOf(Tasks.STATUS to 99), task) + assertEquals(Status(Status.VALUE_NEEDS_ACTION), task.status) + } + +} From 73c9974028e793b61e1b4b9679e8846b23093646 Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Fri, 22 May 2026 13:27:34 +0200 Subject: [PATCH 4/6] Implement CompletedHandler with unit test --- .../mapping/tasks/DmfsTaskProcessor.kt | 5 +-- .../mapping/tasks/handler/CompletedHandler.kt | 23 +++++++++++ .../tasks/handler/CompletedHandlerTest.kt | 41 +++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/CompletedHandler.kt create mode 100644 lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/CompletedHandlerTest.kt diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt index f6abb3ec..6f31a4e0 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt @@ -12,6 +12,7 @@ import at.bitfire.ical4android.UnknownProperty import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import at.bitfire.synctools.mapping.tasks.handler.AlarmsHandler import at.bitfire.synctools.mapping.tasks.handler.ClassificationHandler +import at.bitfire.synctools.mapping.tasks.handler.CompletedHandler import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskFieldHandler import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskPropertyHandler import at.bitfire.synctools.mapping.tasks.handler.PriorityHandler @@ -24,7 +25,6 @@ import at.bitfire.synctools.storage.tasks.DmfsTaskList import at.bitfire.synctools.util.AndroidTimeUtils import net.fortuna.ical4j.model.TimeZoneRegistryFactory import net.fortuna.ical4j.model.parameter.RelType -import net.fortuna.ical4j.model.property.Completed import net.fortuna.ical4j.model.property.DtStart import net.fortuna.ical4j.model.property.Due import net.fortuna.ical4j.model.property.Duration @@ -41,7 +41,6 @@ import org.dmfs.tasks.contract.TaskContract.Property.Comment import org.dmfs.tasks.contract.TaskContract.Property.Relation import org.dmfs.tasks.contract.TaskContract.Tasks import java.net.URISyntaxException -import java.time.Instant import java.time.temporal.Temporal import java.util.logging.Level import java.util.logging.Logger @@ -61,6 +60,7 @@ class DmfsTaskProcessor( PriorityHandler(), ClassificationHandler(), StatusHandler(), + CompletedHandler(), ) private val propertyHandlers: Map = mapOf( @@ -100,7 +100,6 @@ class DmfsTaskProcessor( // Note: big method – maybe split? Depends on how we want to proceed with refactoring. - values.getAsLong(Tasks.COMPLETED)?.let { to.completedAt = Completed(Instant.ofEpochMilli(it)) } values.getAsInteger(Tasks.PERCENT_COMPLETE)?.let { to.percentComplete = it } val allDay = (values.getAsInteger(Tasks.IS_ALLDAY) ?: 0) != 0 diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/CompletedHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/CompletedHandler.kt new file mode 100644 index 00000000..e7e0e1a4 --- /dev/null +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/CompletedHandler.kt @@ -0,0 +1,23 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import at.bitfire.ical4android.Task +import net.fortuna.ical4j.model.property.Completed +import org.dmfs.tasks.contract.TaskContract.Tasks +import java.time.Instant + +class CompletedHandler : DmfsTaskFieldHandler { + + override fun process(from: ContentValues, to: Task) { + from.getAsLong(Tasks.COMPLETED)?.let { epochMillis -> + to.completedAt = Completed(Instant.ofEpochMilli(epochMillis)) + } + } + +} diff --git a/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/CompletedHandlerTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/CompletedHandlerTest.kt new file mode 100644 index 00000000..a9dcdfb6 --- /dev/null +++ b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/CompletedHandlerTest.kt @@ -0,0 +1,41 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import androidx.core.content.contentValuesOf +import at.bitfire.ical4android.Task +import net.fortuna.ical4j.model.property.Completed +import org.dmfs.tasks.contract.TaskContract.Tasks +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import java.time.Instant + +@RunWith(RobolectricTestRunner::class) +class CompletedHandlerTest { + + private val handler = CompletedHandler() + + @Test + fun `No COMPLETED leaves completedAt null`() { + val task = Task() + handler.process(ContentValues(), task) + assertNull(task.completedAt) + } + + @Test + fun `COMPLETED epoch millis is mapped correctly`() { + val task = Task() + val epochMillis = 1_700_000_000_000L + handler.process(contentValuesOf(Tasks.COMPLETED to epochMillis), task) + assertEquals(Completed(Instant.ofEpochMilli(epochMillis)), task.completedAt) + } + +} From 2bac41ddf3bc9c06ff5a0e7b9987438c21975b2e Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Fri, 22 May 2026 13:28:11 +0200 Subject: [PATCH 5/6] Implement PercentCompleteHandler with unit test --- .../mapping/tasks/DmfsTaskProcessor.kt | 4 +- .../tasks/handler/PercentCompleteHandler.kt | 21 +++++++++ .../handler/PercentCompleteHandlerTest.kt | 45 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/PercentCompleteHandler.kt create mode 100644 lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PercentCompleteHandlerTest.kt diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt index 6f31a4e0..c30835ee 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt @@ -15,6 +15,7 @@ import at.bitfire.synctools.mapping.tasks.handler.ClassificationHandler import at.bitfire.synctools.mapping.tasks.handler.CompletedHandler import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskFieldHandler import at.bitfire.synctools.mapping.tasks.handler.DmfsTaskPropertyHandler +import at.bitfire.synctools.mapping.tasks.handler.PercentCompleteHandler import at.bitfire.synctools.mapping.tasks.handler.PriorityHandler import at.bitfire.synctools.mapping.tasks.handler.SequenceHandler import at.bitfire.synctools.mapping.tasks.handler.StatusHandler @@ -61,6 +62,7 @@ class DmfsTaskProcessor( ClassificationHandler(), StatusHandler(), CompletedHandler(), + PercentCompleteHandler(), ) private val propertyHandlers: Map = mapOf( @@ -100,8 +102,6 @@ class DmfsTaskProcessor( // Note: big method – maybe split? Depends on how we want to proceed with refactoring. - values.getAsInteger(Tasks.PERCENT_COMPLETE)?.let { to.percentComplete = it } - val allDay = (values.getAsInteger(Tasks.IS_ALLDAY) ?: 0) != 0 val tzID = values.getAsString(Tasks.TZ) diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/PercentCompleteHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/PercentCompleteHandler.kt new file mode 100644 index 00000000..d6b09887 --- /dev/null +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/handler/PercentCompleteHandler.kt @@ -0,0 +1,21 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import at.bitfire.ical4android.Task +import org.dmfs.tasks.contract.TaskContract.Tasks + +class PercentCompleteHandler : DmfsTaskFieldHandler { + + override fun process(from: ContentValues, to: Task) { + from.getAsInteger(Tasks.PERCENT_COMPLETE)?.let { percent -> + to.percentComplete = percent + } + } + +} diff --git a/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PercentCompleteHandlerTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PercentCompleteHandlerTest.kt new file mode 100644 index 00000000..ff7600dd --- /dev/null +++ b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PercentCompleteHandlerTest.kt @@ -0,0 +1,45 @@ +/* + * This file is part of bitfireAT/synctools which is released under GPLv3. + * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package at.bitfire.synctools.mapping.tasks.handler + +import android.content.ContentValues +import androidx.core.content.contentValuesOf +import at.bitfire.ical4android.Task +import org.dmfs.tasks.contract.TaskContract.Tasks +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class PercentCompleteHandlerTest { + + private val handler = PercentCompleteHandler() + + @Test + fun `No PERCENT_COMPLETE leaves percentComplete null`() { + val task = Task() + handler.process(ContentValues(), task) + assertNull(task.percentComplete) + } + + @Test + fun `PERCENT_COMPLETE 0 is mapped correctly`() { + val task = Task() + handler.process(contentValuesOf(Tasks.PERCENT_COMPLETE to 0), task) + assertEquals(0, task.percentComplete) + } + + @Test + fun `PERCENT_COMPLETE 100 is mapped correctly`() { + val task = Task() + handler.process(contentValuesOf(Tasks.PERCENT_COMPLETE to 100), task) + assertEquals(100, task.percentComplete) + } + +} From 9c379db09a926bea3806f66f15a1d93da88087d6 Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Fri, 22 May 2026 13:29:52 +0200 Subject: [PATCH 6/6] Fix PriorityHandlerTest and restore Instant import in DmfsTaskProcessor --- .../at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt | 1 + .../synctools/mapping/tasks/handler/PriorityHandlerTest.kt | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt index c30835ee..bce83537 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt @@ -42,6 +42,7 @@ import org.dmfs.tasks.contract.TaskContract.Property.Comment import org.dmfs.tasks.contract.TaskContract.Property.Relation import org.dmfs.tasks.contract.TaskContract.Tasks import java.net.URISyntaxException +import java.time.Instant import java.time.temporal.Temporal import java.util.logging.Level import java.util.logging.Logger diff --git a/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandlerTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandlerTest.kt index 3daf9fd7..d317cfa1 100644 --- a/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandlerTest.kt +++ b/lib/src/test/kotlin/at/bitfire/synctools/mapping/tasks/handler/PriorityHandlerTest.kt @@ -11,7 +11,6 @@ import androidx.core.content.contentValuesOf import at.bitfire.ical4android.Task import org.dmfs.tasks.contract.TaskContract.Tasks import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -22,10 +21,10 @@ class PriorityHandlerTest { private val handler = PriorityHandler() @Test - fun `No PRIORITY`() { + fun `No PRIORITY leaves priority at default (0)`() { val task = Task() handler.process(ContentValues(), task) - assertNull(task.priority) + assertEquals(0, task.priority) } @Test