From 9cf4ec417cca2a0807930c6f56a430ecef262ada Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Wed, 8 Apr 2026 06:13:43 -0500 Subject: [PATCH] Remove JvmBaseUnitTest and move chart and DateUtils tests to commonTest --- .../org/isoron/platform/gui/ViewTestHelper.kt | 43 +++++++++++ .../isoron/platform/io/TestPlatformHelper.kt | 5 ++ .../uhabits/core/io/HabitsCSVExporterTest.kt | 2 +- .../habits/list/ListHabitsBehaviorTest.kt | 1 - .../uhabits/core/ui/views/BarChartTest.kt | 7 +- .../uhabits/core/ui/views/HistoryChartTest.kt | 7 +- .../org/isoron/platform/gui/JavaCanvasTest.kt | 6 +- .../isoron/platform/io/TestPlatformHelper.kt | 16 ++++ .../utils => platform/time}/DateUtilsTest.kt | 29 +------ .../org/isoron/platform/time/TestHelpers.kt | 11 +++ .../isoron/uhabits/core/JvmBaseUnitTest.kt | 75 ------------------- .../core/reminders/ReminderSchedulerTest.kt | 12 +-- .../uhabits/core/utils/MidnightTimerTest.kt | 6 +- 13 files changed, 93 insertions(+), 127 deletions(-) create mode 100644 uhabits-core/src/commonTest/kotlin/org/isoron/platform/gui/ViewTestHelper.kt rename uhabits-core/src/{jvmTest/java => commonTest/kotlin}/org/isoron/uhabits/core/ui/views/BarChartTest.kt (92%) rename uhabits-core/src/{jvmTest/java => commonTest/kotlin}/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt (97%) rename uhabits-core/src/jvmTest/java/org/isoron/{uhabits/core/utils => platform/time}/DateUtilsTest.kt (93%) create mode 100644 uhabits-core/src/jvmTest/java/org/isoron/platform/time/TestHelpers.kt delete mode 100644 uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/JvmBaseUnitTest.kt diff --git a/uhabits-core/src/commonTest/kotlin/org/isoron/platform/gui/ViewTestHelper.kt b/uhabits-core/src/commonTest/kotlin/org/isoron/platform/gui/ViewTestHelper.kt new file mode 100644 index 00000000..4b6433b8 --- /dev/null +++ b/uhabits-core/src/commonTest/kotlin/org/isoron/platform/gui/ViewTestHelper.kt @@ -0,0 +1,43 @@ +package org.isoron.platform.gui + +import org.isoron.platform.io.createTestCanvas +import org.isoron.platform.io.createTestFileOpener +import kotlin.test.fail + +suspend fun assertRenders( + path: String, + canvas: Canvas +) { + val actualImage = canvas.toImage() + val failedActualPath = "/tmp/failed/$path" + val failedExpectedPath = failedActualPath.replace(".png", ".expected.png") + val failedDiffPath = failedActualPath.replace(".png", ".diff.png") + val fileOpener = createTestFileOpener() + val expectedFile = fileOpener.openResourceFile(path) + if (expectedFile.exists()) { + val expectedImage = expectedFile.toImage() + val diffImage = expectedFile.toImage() + diffImage.diff(actualImage) + val distance = diffImage.averageLuminosity * 100 + if (distance >= 1.0) { + expectedImage.export(failedExpectedPath) + actualImage.export(failedActualPath) + diffImage.export(failedDiffPath) + fail("Images differ (distance=$distance)") + } + } else { + actualImage.export(failedActualPath) + fail("Expected image file is missing. Actual image: $failedActualPath") + } +} + +suspend fun assertRenders( + width: Int, + height: Int, + expectedPath: String, + view: View +) { + val canvas = createTestCanvas(width, height) + view.draw(canvas) + assertRenders(expectedPath, canvas) +} diff --git a/uhabits-core/src/commonTest/kotlin/org/isoron/platform/io/TestPlatformHelper.kt b/uhabits-core/src/commonTest/kotlin/org/isoron/platform/io/TestPlatformHelper.kt index fd23154f..36f1ffde 100644 --- a/uhabits-core/src/commonTest/kotlin/org/isoron/platform/io/TestPlatformHelper.kt +++ b/uhabits-core/src/commonTest/kotlin/org/isoron/platform/io/TestPlatformHelper.kt @@ -1,4 +1,9 @@ package org.isoron.platform.io +import org.isoron.platform.gui.Canvas +import org.isoron.platform.time.LocalDateFormatter + expect fun createTestFileOpener(): FileOpener expect fun createTestDatabaseOpener(): DatabaseOpener +expect fun createTestCanvas(width: Int, height: Int): Canvas +expect fun createTestDateFormatter(): LocalDateFormatter diff --git a/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/io/HabitsCSVExporterTest.kt b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/io/HabitsCSVExporterTest.kt index 96d74329..42a7a90c 100644 --- a/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/io/HabitsCSVExporterTest.kt +++ b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/io/HabitsCSVExporterTest.kt @@ -23,9 +23,9 @@ import org.isoron.platform.io.ZipReader import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.models.Habit import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -import kotlin.test.assertEquals class HabitsCSVExporterTest : BaseUnitTest() { diff --git a/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt index 1e2cdaf9..408d6c37 100644 --- a/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt +++ b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt @@ -24,7 +24,6 @@ import dev.mokkery.answering.throws import dev.mokkery.every import dev.mokkery.matcher.any import dev.mokkery.mock -import dev.mokkery.resetCalls import dev.mokkery.spy import dev.mokkery.verify import kotlinx.coroutines.runBlocking diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/views/BarChartTest.kt similarity index 92% rename from uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt rename to uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/views/BarChartTest.kt index 541264f4..a952c319 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt +++ b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/views/BarChartTest.kt @@ -21,15 +21,14 @@ package org.isoron.uhabits.core.ui.views import kotlinx.coroutines.runBlocking import org.isoron.platform.gui.assertRenders -import org.isoron.platform.time.JavaLocalDateFormatter +import org.isoron.platform.io.createTestDateFormatter import org.isoron.platform.time.LocalDate -import org.junit.Test -import java.util.Locale +import kotlin.test.Test class BarChartTest { val base = "views/BarChart" val today = LocalDate(2015, 1, 25) - private val fmt = JavaLocalDateFormatter(Locale.US) + private val fmt = createTestDateFormatter() val theme = LightTheme() val component = BarChart(theme, fmt) private val axis = (0..100).map { today.minus(it) } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt similarity index 97% rename from uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt rename to uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt index 25de55de..6bcefeb8 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt +++ b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt @@ -25,17 +25,16 @@ import dev.mokkery.verify import dev.mokkery.verifyNoMoreCalls import kotlinx.coroutines.runBlocking import org.isoron.platform.gui.assertRenders +import org.isoron.platform.io.createTestDateFormatter import org.isoron.platform.time.DayOfWeek import org.isoron.platform.time.DayOfWeek.SUNDAY -import org.isoron.platform.time.JavaLocalDateFormatter import org.isoron.platform.time.LocalDate import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.ui.views.HistoryChart.Square.DIMMED import org.isoron.uhabits.core.ui.views.HistoryChart.Square.HATCHED import org.isoron.uhabits.core.ui.views.HistoryChart.Square.OFF import org.isoron.uhabits.core.ui.views.HistoryChart.Square.ON -import org.junit.Test -import java.util.Locale +import kotlin.test.Test class HistoryChartTest { val base = "views/HistoryChart" @@ -46,7 +45,7 @@ class HistoryChartTest { today = LocalDate(2015, 1, 25), paletteColor = PaletteColor(7), theme = LightTheme(), - dateFormatter = JavaLocalDateFormatter(Locale.US), + dateFormatter = createTestDateFormatter(), firstWeekday = SUNDAY, onDateClickedListener = dateClickedListener, defaultSquare = OFF, diff --git a/uhabits-core/src/jvmTest/java/org/isoron/platform/gui/JavaCanvasTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/platform/gui/JavaCanvasTest.kt index c7333608..fb51b952 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/platform/gui/JavaCanvasTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/platform/gui/JavaCanvasTest.kt @@ -33,9 +33,9 @@ class JavaCanvasTest { } } -fun createCanvas(w: Int, h: Int) = JavaCanvas(BufferedImage(2 * w, 2 * h, TYPE_INT_ARGB), 2.0) +private fun createCanvas(w: Int, h: Int) = JavaCanvas(BufferedImage(2 * w, 2 * h, TYPE_INT_ARGB), 2.0) -suspend fun assertRenders( +private suspend fun assertRenders( path: String, canvas: Canvas ) { @@ -65,7 +65,7 @@ suspend fun assertRenders( } } -suspend fun assertRenders( +private suspend fun assertRenders( width: Int, height: Int, expectedPath: String, diff --git a/uhabits-core/src/jvmTest/java/org/isoron/platform/io/TestPlatformHelper.kt b/uhabits-core/src/jvmTest/java/org/isoron/platform/io/TestPlatformHelper.kt index 8515f97d..08b71ecf 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/platform/io/TestPlatformHelper.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/platform/io/TestPlatformHelper.kt @@ -1,4 +1,20 @@ package org.isoron.platform.io +import org.isoron.platform.gui.Canvas +import org.isoron.platform.gui.JavaCanvas +import org.isoron.platform.time.JavaLocalDateFormatter +import org.isoron.platform.time.LocalDateFormatter +import java.awt.image.BufferedImage +import java.awt.image.BufferedImage.TYPE_INT_ARGB +import java.util.Locale + actual fun createTestFileOpener(): FileOpener = JavaFileOpener() actual fun createTestDatabaseOpener(): DatabaseOpener = JavaDatabaseOpener() + +actual fun createTestCanvas(width: Int, height: Int): Canvas { + return JavaCanvas(BufferedImage(2 * width, 2 * height, TYPE_INT_ARGB), 2.0) +} + +actual fun createTestDateFormatter(): LocalDateFormatter { + return JavaLocalDateFormatter(Locale.US) +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/platform/time/DateUtilsTest.kt similarity index 93% rename from uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt rename to uhabits-core/src/jvmTest/java/org/isoron/platform/time/DateUtilsTest.kt index a8700fcd..4cc32ee3 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/platform/time/DateUtilsTest.kt @@ -1,29 +1,9 @@ -/* - * Copyright (C) 2016-2025 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ -package org.isoron.uhabits.core.utils +package org.isoron.platform.time -import org.isoron.platform.time.DateUtils import org.junit.After import org.junit.Before import org.junit.Test import java.util.Calendar -import java.util.GregorianCalendar import java.util.TimeZone import kotlin.test.assertEquals @@ -45,13 +25,6 @@ class DateUtilsTest { DateUtils.setFixedTimeZone(null) } - private fun unixTime(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0): Long { - val cal = GregorianCalendar(TimeZone.getTimeZone("GMT")) - cal.set(year, month, day, hour, minute, 0) - cal.set(GregorianCalendar.MILLISECOND, 0) - return cal.timeInMillis - } - // --------------------------------------------------------------- // getLocalTime // --------------------------------------------------------------- diff --git a/uhabits-core/src/jvmTest/java/org/isoron/platform/time/TestHelpers.kt b/uhabits-core/src/jvmTest/java/org/isoron/platform/time/TestHelpers.kt new file mode 100644 index 00000000..dfad9c70 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/platform/time/TestHelpers.kt @@ -0,0 +1,11 @@ +package org.isoron.platform.time + +import java.util.GregorianCalendar +import java.util.TimeZone + +fun unixTime(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, milliseconds: Long = 0): Long { + val cal = GregorianCalendar(TimeZone.getTimeZone("GMT")) + cal.set(year, month, day, hour, minute, 0) + cal.set(GregorianCalendar.MILLISECOND, 0) + return cal.timeInMillis + milliseconds +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/JvmBaseUnitTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/JvmBaseUnitTest.kt deleted file mode 100644 index 6e61faac..00000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/JvmBaseUnitTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2016-2025 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ -package org.isoron.uhabits.core - -import dev.mokkery.spy -import org.apache.commons.io.IOUtils -import org.isoron.uhabits.core.models.memory.MemoryModelFactory -import org.isoron.uhabits.core.test.HabitFixtures -import org.junit.Before -import org.junit.Test -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream -import java.nio.file.Paths -import java.util.GregorianCalendar -import java.util.TimeZone - -open class JvmBaseUnitTest : BaseUnitTest() { - @Before - override fun setUp() { - super.setUp() - habitList = spy(habitList) - fixtures = HabitFixtures(modelFactory as MemoryModelFactory, habitList) - } - - fun unixTime(year: Int, month: Int, day: Int): Long { - return unixTime(year, month, day, 0, 0) - } - - open fun unixTime(year: Int, month: Int, day: Int, hour: Int, minute: Int, milliseconds: Long = 0): Long { - val cal = GregorianCalendar(TimeZone.getTimeZone("GMT")) - cal.set(year, month, day, hour, minute, 0) - cal.set(GregorianCalendar.MILLISECOND, 0) - return cal.timeInMillis + milliseconds - } - - @Test - fun nothing() { - } - - @Throws(IOException::class) - protected fun copyAssetToFile(assetPath: String, dst: File?) { - IOUtils.copy(openAsset(assetPath), FileOutputStream(dst!!)) - } - - @Throws(IOException::class) - protected fun openAsset(assetPath: String): InputStream { - var inputStream = javaClass.getResourceAsStream(assetPath) - if (inputStream != null) return inputStream - val pwd = Paths.get(".").toAbsolutePath().normalize().toString() - val fullPath = "$pwd/assets/test/$assetPath" - val file = File(fullPath) - if (file.exists() && file.canRead()) inputStream = FileInputStream(file) - if (inputStream != null) return inputStream - throw IllegalStateException("asset not found: $fullPath") - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt index e256f815..e93b6ce3 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt @@ -27,7 +27,8 @@ import org.isoron.platform.time.DateUtils import org.isoron.platform.time.DateUtils.removeTimezone import org.isoron.platform.time.DateUtils.setFixedLocalTime import org.isoron.platform.time.DateUtils.setFixedTimeZone -import org.isoron.uhabits.core.JvmBaseUnitTest +import org.isoron.platform.time.unixTime +import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.WeekdayList @@ -37,7 +38,7 @@ import org.junit.Before import org.junit.Test import java.util.TimeZone -class ReminderSchedulerTest : JvmBaseUnitTest() { +class ReminderSchedulerTest : BaseUnitTest() { private val habitId = 10L private lateinit var habit: Habit private lateinit var reminderScheduler: ReminderScheduler @@ -145,13 +146,6 @@ class ReminderSchedulerTest : JvmBaseUnitTest() { reminderScheduler.schedule(habit) } - override fun unixTime(year: Int, month: Int, day: Int, hour: Int, minute: Int, milliseconds: Long): Long { - val cal = java.util.GregorianCalendar(TimeZone.getTimeZone("GMT")) - cal.set(year, month, day, hour, minute, 0) - cal.set(java.util.GregorianCalendar.MILLISECOND, 0) - return cal.timeInMillis - } - private fun scheduleAndVerify( atTime: Long?, expectedCheckmarkTime: Long, diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt index 99396d37..12b91887 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt @@ -5,7 +5,8 @@ import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.isoron.platform.time.DateUtils -import org.isoron.uhabits.core.JvmBaseUnitTest +import org.isoron.platform.time.unixTime +import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.io.StandardLogging import org.isoron.uhabits.core.preferences.Preferences import org.junit.After @@ -17,7 +18,7 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlin.test.assertEquals -class MidnightTimerTest : JvmBaseUnitTest() { +class MidnightTimerTest : BaseUnitTest() { @After fun tearDown() { @@ -56,4 +57,5 @@ class MidnightTimerTest : JvmBaseUnitTest() { assertEquals(true, suspendedListener) } } + }