Move test sources from jvmTest to commonTest

This commit is contained in:
Alinson S. Xavier 2026-04-08 05:27:33 -05:00
parent b37af7949c
commit a93e871daf
19 changed files with 336 additions and 318 deletions

View File

@ -93,6 +93,17 @@ interface UserFile {
* parent directory (or this file itself, if it represents a directory).
*/
fun resolve(child: String): UserFile
/**
* Returns the list of files and directories within this directory, or null
* if this path is not a directory or does not exist.
*/
suspend fun listFiles(): List<UserFile>?
/**
* Creates this directory and any necessary parent directories.
*/
suspend fun mkdirs()
}
/**

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2016-2025 Álinson Santos Xavier <git@axavier.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.isoron.platform.io
class ZipEntry(val name: String, val content: String)
expect class ZipReader(bytes: ByteArray) {
fun entries(): List<ZipEntry>
}

View File

@ -0,0 +1,4 @@
package org.isoron.platform.io
expect fun createTestFileOpener(): FileOpener
expect fun createTestDatabaseOpener(): DatabaseOpener

View File

@ -18,7 +18,14 @@
*/
package org.isoron.uhabits.core
import kotlinx.coroutines.runBlocking
import org.isoron.platform.io.Database
import org.isoron.platform.io.DatabaseOpener
import org.isoron.platform.io.FileOpener
import org.isoron.platform.io.TestDatabaseHelper
import org.isoron.platform.io.UserFile
import org.isoron.platform.io.createTestDatabaseOpener
import org.isoron.platform.io.createTestFileOpener
import org.isoron.platform.time.LocalDate
import org.isoron.platform.time.setToday
import org.isoron.uhabits.core.commands.CommandRunner
@ -35,6 +42,8 @@ open class BaseUnitTest {
protected lateinit var modelFactory: ModelFactory
protected lateinit var taskRunner: SingleThreadTaskRunner
protected open lateinit var commandRunner: CommandRunner
protected val fileOpener: FileOpener = createTestFileOpener()
protected val databaseOpener: DatabaseOpener = createTestDatabaseOpener()
@BeforeTest
open fun setUp() {
@ -47,8 +56,28 @@ open class BaseUnitTest {
commandRunner = CommandRunner(taskRunner)
}
protected fun createTempDir(): UserFile = runBlocking {
val dir = fileOpener.openUserFile("test-temp-dir-${tempFileCounter++}")
dir.mkdirs()
dir
}
protected fun copyResourceToTempFile(resourcePath: String): UserFile = runBlocking {
val cleanPath = resourcePath.removePrefix("/")
val tempFile = fileOpener.openUserFile("test-temp-${tempFileCounter++}")
fileOpener.openResourceFile(cleanPath).copyTo(tempFile)
tempFile
}
protected fun openDatabaseResource(resourcePath: String): Database = runBlocking {
val tempFile = copyResourceToTempFile(resourcePath)
databaseOpener.open(tempFile.pathString)
}
companion object {
fun buildMemoryDatabase(): org.isoron.platform.io.Database {
private var tempFileCounter = 0
fun buildMemoryDatabase(): Database {
return TestDatabaseHelper.createEmptyDatabase()
}
}

View File

@ -19,29 +19,24 @@
package org.isoron.uhabits.core.database.migrations
import kotlinx.coroutines.runBlocking
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.hamcrest.Matchers.equalTo
import org.isoron.platform.io.Database
import org.isoron.platform.io.JavaFileOpener
import org.isoron.platform.io.migrateTo
import org.isoron.platform.io.querySingle
import org.isoron.platform.io.run
import org.isoron.uhabits.core.JvmBaseUnitTest
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertThrows
import org.isoron.uhabits.core.BaseUnitTest
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class Version22Test : JvmBaseUnitTest() {
class Version22Test : BaseUnitTest() {
private lateinit var db: Database
@Throws(Exception::class)
override fun setUp() {
super.setUp()
db = openDatabaseResource("/databases/021.db")
}
private val fileOpener = JavaFileOpener()
private fun migrateTo(version: Int) = runBlocking {
db.migrateTo(version) { v ->
val path = "migrations/%02d.sql".format(v)
@ -52,10 +47,10 @@ class Version22Test : JvmBaseUnitTest() {
@Test
fun testKeepValidReps() {
val before = db.querySingle("select count(*) from repetitions") { it.getInt(0) }
assertThat(before, equalTo(3))
assertEquals(3, before)
migrateTo(22)
val after = db.querySingle("select count(*) from repetitions") { it.getInt(0) }
assertThat(after, equalTo(3))
assertEquals(3, after)
}
@Test
@ -64,21 +59,21 @@ class Version22Test : JvmBaseUnitTest() {
val before = db.querySingle(
"select count(*) from repetitions where habit = 99999"
) { it.getInt(0) }
assertThat(before, equalTo(1))
assertEquals(1, before)
migrateTo(22)
val after = db.querySingle(
"select count(*) from repetitions where habit = 99999"
) { it.getInt(0) }
assertThat(after, equalTo(0))
assertEquals(0, after)
}
@Test
fun testDisallowNewRepsWithInvalidRef() {
migrateTo(22)
val exception = assertThrows(Exception::class.java) {
val exception = assertFailsWith<Exception> {
db.run("insert into Repetitions(habit, timestamp, value) values (99999, 100, 2)")
}
assertThat(exception.message, Matchers.containsString("constraint"))
assertContains(exception.message!!, "constraint")
}
@Test
@ -87,21 +82,21 @@ class Version22Test : JvmBaseUnitTest() {
val before = db.querySingle(
"select count(*) from repetitions where timestamp is null"
) { it.getInt(0) }
assertThat(before, equalTo(1))
assertEquals(1, before)
migrateTo(22)
val after = db.querySingle(
"select count(*) from repetitions where timestamp is null"
) { it.getInt(0) }
assertThat(after, equalTo(0))
assertEquals(0, after)
}
@Test
fun testDisallowNullTimestamp() {
migrateTo(22)
val exception = assertThrows(Exception::class.java) {
val exception = assertFailsWith<Exception> {
db.run("insert into Repetitions(habit, value) values (0, 2)")
}
assertThat(exception.message, Matchers.containsString("constraint"))
assertContains(exception.message!!, "constraint")
}
@Test
@ -110,21 +105,21 @@ class Version22Test : JvmBaseUnitTest() {
val before = db.querySingle(
"select count(*) from repetitions where habit is null"
) { it.getInt(0) }
assertThat(before, equalTo(1))
assertEquals(1, before)
migrateTo(22)
val after = db.querySingle(
"select count(*) from repetitions where habit is null"
) { it.getInt(0) }
assertThat(after, equalTo(0))
assertEquals(0, after)
}
@Test
fun testDisallowNullHabit() {
migrateTo(22)
val exception = assertThrows(Exception::class.java) {
val exception = assertFailsWith<Exception> {
db.run("insert into Repetitions(timestamp, value) values (5, 2)")
}
assertThat(exception.message, Matchers.containsString("constraint"))
assertContains(exception.message!!, "constraint")
}
@Test
@ -135,21 +130,21 @@ class Version22Test : JvmBaseUnitTest() {
val before = db.querySingle(
"select count(*) from repetitions where timestamp=100 and habit=0"
) { it.getInt(0) }
assertThat(before, equalTo(3))
assertEquals(3, before)
migrateTo(22)
val after = db.querySingle(
"select count(*) from repetitions where timestamp=100 and habit=0"
) { it.getInt(0) }
assertThat(after, equalTo(1))
assertEquals(1, after)
}
@Test
fun testDisallowNewDuplicateTimestamps() {
migrateTo(22)
db.run("insert into repetitions(habit, timestamp, value)values (0, 100, 2)")
val exception = assertThrows(Exception::class.java) {
val exception = assertFailsWith<Exception> {
db.run("insert into repetitions(habit, timestamp, value)values (0, 100, 5)")
}
assertThat(exception.message, Matchers.containsString("constraint"))
assertContains(exception.message!!, "constraint")
}
}

View File

@ -20,16 +20,14 @@
package org.isoron.uhabits.core.database.migrations
import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.platform.io.Database
import org.isoron.platform.io.JavaFileOpener
import org.isoron.platform.io.migrateTo
import org.isoron.platform.io.query
import org.isoron.uhabits.core.JvmBaseUnitTest
import org.junit.Test
import org.isoron.uhabits.core.BaseUnitTest
import kotlin.test.Test
import kotlin.test.assertEquals
class Version23Test : JvmBaseUnitTest() {
class Version23Test : BaseUnitTest() {
private lateinit var db: Database
@ -38,8 +36,6 @@ class Version23Test : JvmBaseUnitTest() {
db = openDatabaseResource("/databases/022.db")
}
private val fileOpener = JavaFileOpener()
private fun migrateTo(version: Int) = runBlocking {
db.migrateTo(version) { v ->
val path = "migrations/%02d.sql".format(v)
@ -48,13 +44,13 @@ class Version23Test : JvmBaseUnitTest() {
}
@Test
fun `test migrate to 23 creates question column`() {
fun testMigrateTo23CreatesQuestionColumn() {
migrateTo(23)
db.query("select question from Habits") {}
}
@Test
fun `test migrate to 23 moves description to question column`() {
fun testMigrateTo23MovesDescriptionToQuestionColumn() {
val descriptions = mutableListOf<String?>()
db.query("select description from Habits") { stmt ->
descriptions.add(stmt.getTextOrNull(0))
@ -68,15 +64,15 @@ class Version23Test : JvmBaseUnitTest() {
}
for (i in descriptions.indices) {
assertThat(questions[i], equalTo(descriptions[i]))
assertEquals(descriptions[i], questions[i])
}
}
@Test
fun `test migrate to 23 sets description to null`() {
fun testMigrateTo23SetsDescriptionToNull() {
migrateTo(23)
db.query("select description from Habits") { stmt ->
assertThat(stmt.getTextOrNull(0), equalTo(""))
assertEquals("", stmt.getTextOrNull(0))
}
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2016-2025 Álinson Santos Xavier <git@axavier.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.io
import kotlinx.coroutines.runBlocking
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.assertNotNull
import kotlin.test.assertTrue
import kotlin.test.assertEquals
class HabitsCSVExporterTest : BaseUnitTest() {
override fun setUp() {
super.setUp()
habitList.add(fixtures.createShortHabit())
habitList.add(fixtures.createEmptyHabit())
}
@Test
fun testExportCSV() = runBlocking {
val selected: MutableList<Habit> = mutableListOf()
for (h in habitList) selected.add(h)
val exporter = HabitsCSVExporter(habitList, selected)
val bytes = exporter.writeArchive()
assertTrue(bytes.isNotEmpty())
val entries = ZipReader(bytes).entries()
val filesToCheck = arrayOf(
"001 Meditate/Checkmarks.csv",
"001 Meditate/Scores.csv",
"002 Wake up early/Checkmarks.csv",
"002 Wake up early/Scores.csv",
"Checkmarks.csv",
"Habits.csv",
"Scores.csv"
)
for (file in filesToCheck) {
val entry = entries.find { it.name == file }
assertNotNull(entry, "$file should exist in zip")
val expected = fileOpener.openResourceFile("csv_export/$file").lines()
assertEquals(expected, entry.content.trimEnd().lines(), "content mismatch for $file")
}
}
}

View File

@ -19,39 +19,27 @@
package org.isoron.uhabits.core.io
import kotlinx.coroutines.runBlocking
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual.equalTo
import org.isoron.platform.io.JavaFileOpener
import org.isoron.platform.io.JavaUserFile
import org.isoron.platform.time.LocalDate
import org.isoron.uhabits.core.JvmBaseUnitTest
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitType
import org.junit.Before
import org.junit.Test
import java.io.File
import java.io.IOException
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ImportTest : JvmBaseUnitTest() {
@Before
@Throws(Exception::class)
override fun setUp() {
super.setUp()
}
class ImportTest : BaseUnitTest() {
@Test
@Throws(IOException::class)
fun testHabitBullCSV() {
importFromFile("habitbull.csv")
assertThat(habitList.size(), equalTo(4))
assertEquals(4, habitList.size())
val habit = habitList.getByPosition(0)
assertThat(habit.name, equalTo("Breed dragons"))
assertThat(habit.description, equalTo("with love and fire"))
assertThat(habit.frequency, equalTo(Frequency.DAILY))
assertEquals("Breed dragons", habit.name)
assertEquals("with love and fire", habit.description)
assertEquals(Frequency.DAILY, habit.frequency)
assertTrue(isChecked(habit, 2016, 3, 18))
assertTrue(isChecked(habit, 2016, 3, 19))
assertFalse(isChecked(habit, 2016, 3, 20))
@ -59,14 +47,13 @@ class ImportTest : JvmBaseUnitTest() {
}
@Test
@Throws(IOException::class)
fun testHabitBullCSV2() {
importFromFile("habitbull2.csv")
assertThat(habitList.size(), equalTo(6))
assertEquals(6, habitList.size())
val habit = habitList.getByPosition(2)
assertThat(habit.name, equalTo("H3"))
assertThat(habit.description, equalTo("Habit 3"))
assertThat(habit.frequency, equalTo(Frequency.DAILY))
assertEquals("H3", habit.name)
assertEquals("Habit 3", habit.description)
assertEquals(Frequency.DAILY, habit.frequency)
assertTrue(isChecked(habit, 2019, 4, 11))
assertTrue(isChecked(habit, 2019, 5, 7))
assertFalse(isChecked(habit, 2019, 6, 14))
@ -75,88 +62,83 @@ class ImportTest : JvmBaseUnitTest() {
}
@Test
@Throws(IOException::class)
fun testHabitBullCSV3() {
importFromFile("habitbull3.csv")
assertThat(habitList.size(), equalTo(2))
assertEquals(2, habitList.size())
val habit = habitList.getByPosition(0)
assertThat(habit.name, equalTo("Pushups"))
assertThat(habit.type, equalTo(HabitType.NUMERICAL))
assertThat(habit.description, equalTo(""))
assertThat(habit.frequency, equalTo(Frequency.DAILY))
assertThat(getValue(habit, 2021, 9, 1), equalTo(30000))
assertThat(getValue(habit, 2022, 1, 8), equalTo(100000))
assertEquals("Pushups", habit.name)
assertEquals(HabitType.NUMERICAL, habit.type)
assertEquals("", habit.description)
assertEquals(Frequency.DAILY, habit.frequency)
assertEquals(30000, getValue(habit, 2021, 9, 1))
assertEquals(100000, getValue(habit, 2022, 1, 8))
val habit2 = habitList.getByPosition(1)
assertThat(habit2.name, equalTo("run"))
assertThat(habit2.type, equalTo(HabitType.YES_NO))
assertThat(habit2.description, equalTo(""))
assertThat(habit2.frequency, equalTo(Frequency.DAILY))
assertEquals("run", habit2.name)
assertEquals(HabitType.YES_NO, habit2.type)
assertEquals("", habit2.description)
assertEquals(Frequency.DAILY, habit2.frequency)
assertTrue(isChecked(habit2, 2022, 1, 3))
assertTrue(isChecked(habit2, 2022, 1, 18))
assertTrue(isChecked(habit2, 2022, 1, 19))
}
@Test
@Throws(IOException::class)
fun testHabitBullCSV4() {
importFromFile("habitbull4.csv")
assertThat(habitList.size(), equalTo(1))
assertEquals(1, habitList.size())
val habit = habitList.getByPosition(0)
assertThat(habit.name, equalTo("Caffeine"))
assertThat(habit.type, equalTo(HabitType.NUMERICAL))
assertThat(habit.description, equalTo(""))
assertThat(habit.frequency, equalTo(Frequency.DAILY))
assertThat(getValue(habit, 2022, 11, 21), equalTo(80000))
assertThat(getValue(habit, 2022, 11, 22), equalTo(80000))
assertEquals("Caffeine", habit.name)
assertEquals(HabitType.NUMERICAL, habit.type)
assertEquals("", habit.description)
assertEquals(Frequency.DAILY, habit.frequency)
assertEquals(80000, getValue(habit, 2022, 11, 21))
assertEquals(80000, getValue(habit, 2022, 11, 22))
}
@Test
@Throws(IOException::class)
fun testLoopDB() {
importFromFile("loop.db")
assertThat(habitList.size(), equalTo(9))
assertEquals(9, habitList.size())
val habit = habitList.getByPosition(0)
assertThat(habit.name, equalTo("Wake up early"))
assertThat(habit.frequency, equalTo(Frequency.THREE_TIMES_PER_WEEK))
assertEquals("Wake up early", habit.name)
assertEquals(Frequency.THREE_TIMES_PER_WEEK, habit.frequency)
assertTrue(isChecked(habit, 2016, 3, 14))
assertTrue(isChecked(habit, 2016, 3, 16))
assertFalse(isChecked(habit, 2016, 3, 17))
}
@Test
@Throws(IOException::class)
fun testRewireDB() {
importFromFile("rewire.db")
assertThat(habitList.size(), equalTo(3))
assertEquals(3, habitList.size())
var habit = habitList.getByPosition(1)
assertThat(habit.name, equalTo("Wake up early"))
assertThat(habit.frequency, equalTo(Frequency.THREE_TIMES_PER_WEEK))
assertEquals("Wake up early", habit.name)
assertEquals(Frequency.THREE_TIMES_PER_WEEK, habit.frequency)
assertFalse(habit.hasReminder())
assertFalse(isChecked(habit, 2015, 12, 31))
assertTrue(isChecked(habit, 2016, 1, 18))
assertTrue(isChecked(habit, 2016, 1, 28))
assertFalse(isChecked(habit, 2016, 3, 10))
habit = habitList.getByPosition(2)
assertThat(habit.name, equalTo("brush teeth"))
assertThat(habit.frequency, equalTo(Frequency.THREE_TIMES_PER_WEEK))
assertThat(habit.hasReminder(), equalTo(true))
assertEquals("brush teeth", habit.name)
assertEquals(Frequency.THREE_TIMES_PER_WEEK, habit.frequency)
assertEquals(true, habit.hasReminder())
val reminder = habit.reminder
assertThat(reminder!!.hour, equalTo(8))
assertThat(reminder.minute, equalTo(0))
assertEquals(8, reminder!!.hour)
assertEquals(0, reminder.minute)
val reminderDays = booleanArrayOf(false, true, true, true, true, true, false)
assertThat(reminder.days.toArray(), equalTo(reminderDays))
assertEquals(reminderDays.toList(), reminder.days.toArray().toList())
}
@Test
@Throws(IOException::class)
fun testTickmateDB() {
importFromFile("tickmate.db")
assertThat(habitList.size(), equalTo(3))
assertEquals(3, habitList.size())
val h = habitList.getByPosition(2)
assertThat(h.name, equalTo("Vegan"))
assertEquals("Vegan", h.name)
assertTrue(isChecked(h, 2016, 1, 24))
assertTrue(isChecked(h, 2016, 2, 5))
assertTrue(isChecked(h, 2016, 3, 18))
@ -175,13 +157,9 @@ class ImportTest : JvmBaseUnitTest() {
return h.originalEntries.get(LocalDate(year, month, day)).notes == notes
}
@Throws(IOException::class)
private fun importFromFile(assetFilename: String) = runBlocking {
val file = File.createTempFile("asset", "")
copyAssetToFile(assetFilename, file)
assertTrue(file.exists())
assertTrue(file.canRead())
val userFile = JavaUserFile(file.toPath())
val userFile = copyResourceToTempFile(assetFilename)
assertTrue(userFile.exists())
val importer = GenericImporter(
LoopDBImporter(
habitList,
@ -189,7 +167,7 @@ class ImportTest : JvmBaseUnitTest() {
databaseOpener,
commandRunner,
StandardLogging(),
JavaFileOpener()
fileOpener
),
RewireDBImporter(habitList, modelFactory, databaseOpener),
TickmateDBImporter(habitList, modelFactory, databaseOpener),
@ -197,6 +175,6 @@ class ImportTest : JvmBaseUnitTest() {
)
assertTrue(importer.canHandle(userFile))
importer.importHabitsFromFile(userFile)
file.delete()
userFile.delete()
}
}

View File

@ -20,9 +20,7 @@ package org.isoron.uhabits.core.models.sqlite
import dev.mokkery.mock
import dev.mokkery.verify
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.JvmBaseUnitTest
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.database.HabitRepository
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
@ -31,20 +29,19 @@ import org.isoron.uhabits.core.models.ModelObservable
import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.WeekdayList
import org.isoron.uhabits.core.test.HabitFixtures
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Test
import java.util.ArrayList
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
class SQLiteHabitListTest : JvmBaseUnitTest() {
class SQLiteHabitListTest : BaseUnitTest() {
private lateinit var repository: HabitRepository
private var listener: ModelObservable.Listener = mock()
private lateinit var habitsArray: ArrayList<Habit>
private lateinit var activeHabits: HabitList
private lateinit var reminderHabits: HabitList
@Throws(Exception::class)
override fun setUp() {
super.setUp()
val db = buildMemoryDatabase()
@ -75,7 +72,7 @@ class SQLiteHabitListTest : JvmBaseUnitTest() {
habitList.observable.addListener(listener)
}
@After
@AfterTest
fun tearDown() {
habitList.observable.removeListener(listener)
}
@ -85,7 +82,7 @@ class SQLiteHabitListTest : JvmBaseUnitTest() {
val habit = modelFactory.buildHabit()
habitList.add(habit)
verify { listener.onModelChange() }
assertThrows(IllegalArgumentException::class.java) {
assertFailsWith<IllegalArgumentException> {
habitList.add(habit)
}
}
@ -96,10 +93,10 @@ class SQLiteHabitListTest : JvmBaseUnitTest() {
habit.name = "Hello world with id"
habit.id = 12300L
habitList.add(habit)
assertThat(habit.id, equalTo(12300L))
assertEquals(12300L, habit.id)
val all = repository.findAll()
val record = all.find { it.id == 12300L }
assertThat(record!!.name, equalTo(habit.name))
assertEquals(habit.name, record!!.name)
}
@Test
@ -110,20 +107,20 @@ class SQLiteHabitListTest : JvmBaseUnitTest() {
habitList.add(habit)
val all = repository.findAll()
val record = all.find { it.id == habit.id }
assertThat(record!!.name, equalTo(habit.name))
assertEquals(habit.name, record!!.name)
}
@Test
fun testSize() {
assertThat(habitList.size(), equalTo(10))
assertEquals(10, habitList.size())
}
@Test
fun testGetById() {
val h1 = habitList.getById(1)!!
assertThat(h1.name, equalTo("habit 1"))
assertEquals("habit 1", h1.name)
val h2 = habitList.getById(2)!!
assertThat(h2, equalTo(h2))
assertEquals(h2, h2)
}
@Test
@ -136,31 +133,30 @@ class SQLiteHabitListTest : JvmBaseUnitTest() {
@Test
fun testGetByPosition() {
val h = habitList.getByPosition(4)
assertThat(h.name, equalTo("habit 5"))
assertEquals("habit 5", h.name)
}
@Test
fun testIndexOf() {
val h1 = habitList.getByPosition(5)
assertThat(habitList.indexOf(h1), equalTo(5))
assertEquals(5, habitList.indexOf(h1))
val h2 = modelFactory.buildHabit()
assertThat(habitList.indexOf(h2), equalTo(-1))
assertEquals(-1, habitList.indexOf(h2))
h2.id = 1000L
assertThat(habitList.indexOf(h2), equalTo(-1))
assertEquals(-1, habitList.indexOf(h2))
}
@Test
@Throws(Exception::class)
fun testRemove() {
val h = habitList.getById(2)
habitList.remove(h!!)
assertThat(habitList.indexOf(h), equalTo(-1))
assertEquals(-1, habitList.indexOf(h))
val all = repository.findAll()
val rec2 = all.find { it.id == 2L }
assertNull(rec2)
val rec3 = all.find { it.id == 3L }!!
assertThat(rec3.position, equalTo(1))
assertEquals(1, rec3.position)
}
@Test
@ -168,13 +164,13 @@ class SQLiteHabitListTest : JvmBaseUnitTest() {
habitList.primaryOrder = HabitList.Order.BY_NAME_DESC
val h = habitList.getById(2)
habitList.remove(h!!)
assertThat(habitList.indexOf(h), equalTo(-1))
assertEquals(-1, habitList.indexOf(h))
val all = repository.findAll()
val rec2 = all.find { it.id == 2L }
assertNull(rec2)
val rec3 = all.find { it.id == 3L }!!
assertThat(rec3.position, equalTo(1))
assertEquals(1, rec3.position)
}
@Test
@ -184,8 +180,8 @@ class SQLiteHabitListTest : JvmBaseUnitTest() {
habitList.reorder(habit4, habit3)
val all = repository.findAll()
val record3 = all.find { it.id == 3L }!!
assertThat(record3.position, equalTo(3))
assertEquals(3, record3.position)
val record4 = all.find { it.id == 4L }!!
assertThat(record4.position, equalTo(2))
assertEquals(2, record4.position)
}
}

View File

@ -25,24 +25,22 @@ 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 org.apache.commons.io.FileUtils
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual.equalTo
import org.isoron.platform.io.JavaUserFile
import kotlinx.coroutines.runBlocking
import org.isoron.platform.io.UserFile
import org.isoron.platform.time.getToday
import org.isoron.uhabits.core.JvmBaseUnitTest
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.callbacks.NumberPickerCallback
import org.junit.Before
import org.junit.Test
import java.nio.file.Files
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ListHabitsBehaviorTest : JvmBaseUnitTest() {
class ListHabitsBehaviorTest : BaseUnitTest() {
private val dirFinder: ListHabitsBehavior.DirFinder = mock()
private val prefs: Preferences = mock()
@ -56,15 +54,13 @@ class ListHabitsBehaviorTest : JvmBaseUnitTest() {
private val bugReporter: ListHabitsBehavior.BugReporter = mock()
@Before
@Throws(Exception::class)
override fun setUp() {
super.setUp()
habit1 = fixtures.createShortHabit()
habit2 = fixtures.createNumericalHabit()
habitList.add(habit1)
habitList.add(habit2)
resetCalls(habitList)
habitList = spy(habitList)
behavior = ListHabitsBehavior(
habitList,
dirFinder,
@ -90,29 +86,26 @@ class ListHabitsBehaviorTest : JvmBaseUnitTest() {
screen.showNumberPopup(0.1, "", any())
}
capturedPicker!!.onNumberPicked(100.0, "")
assertThat(habit2.computedEntries.get(today).value, equalTo(100000))
assertEquals(100000, habit2.computedEntries.get(today).value)
}
@Test
@Throws(Exception::class)
fun testOnExportCSV() {
val outputDir = Files.createTempDirectory("CSV").toFile()
every { dirFinder.getCSVOutputDir() } returns JavaUserFile(outputDir.toPath())
val outputDir = createTempDir()
every { dirFinder.getCSVOutputDir() } returns outputDir
behavior.onExportCSV()
verify { screen.showSendFileScreen(any()) }
assertThat(FileUtils.listFiles(outputDir, null, false).size, equalTo(1))
FileUtils.deleteDirectory(outputDir)
val files = runBlocking { outputDir.listFiles() }
assertEquals(1, files!!.size)
}
@Test
@Throws(Exception::class)
fun testOnExportCSV_fail() {
val outputDir = Files.createTempDirectory("CSV").toFile()
outputDir.setWritable(false)
every { dirFinder.getCSVOutputDir() } returns JavaUserFile(outputDir.toPath())
val mockDir: UserFile = mock()
every { mockDir.resolve(any()) } throws RuntimeException("not writable")
every { dirFinder.getCSVOutputDir() } returns mockDir
behavior.onExportCSV()
verify { screen.showMessage(ListHabitsBehavior.Message.COULD_NOT_EXPORT) }
assertTrue(outputDir.delete())
}
@Test

View File

@ -22,22 +22,18 @@ import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.mock
import dev.mokkery.verify
import org.apache.commons.io.FileUtils
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.platform.io.JavaUserFile
import org.isoron.uhabits.core.JvmBaseUnitTest
import kotlinx.coroutines.runBlocking
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit
import org.junit.Test
import java.nio.file.Files
import kotlin.test.Test
import kotlin.test.assertEquals
class ShowHabitMenuPresenterTest : JvmBaseUnitTest() {
class ShowHabitMenuPresenterTest : BaseUnitTest() {
private lateinit var system: ShowHabitMenuPresenter.System
private lateinit var screen: ShowHabitMenuPresenter.Screen
private lateinit var habit: Habit
private lateinit var menu: ShowHabitMenuPresenter
@Throws(Exception::class)
override fun setUp() {
super.setUp()
system = mock()
@ -60,12 +56,11 @@ class ShowHabitMenuPresenterTest : JvmBaseUnitTest() {
}
@Test
@Throws(Exception::class)
fun testOnExport() {
val outputDir = Files.createTempDirectory("CSV").toFile()
every { system.getCSVOutputDir() } returns JavaUserFile(outputDir.toPath())
val outputDir = createTempDir()
every { system.getCSVOutputDir() } returns outputDir
menu.onExportCSV()
assertThat(FileUtils.listFiles(outputDir, null, false).size, equalTo(1))
FileUtils.deleteDirectory(outputDir)
val files = runBlocking { outputDir.listFiles() }
assertEquals(1, files!!.size)
}
}

View File

@ -0,0 +1,16 @@
package org.isoron.uhabits.core.utils
import kotlinx.coroutines.runBlocking
import org.isoron.uhabits.core.BaseUnitTest
import kotlin.test.Test
import kotlin.test.assertTrue
class FileExtensionsTest : BaseUnitTest() {
@Test
fun testIsSQLite3File() = runBlocking {
val userFile = copyResourceToTempFile("loop.db")
val isSqlite3File = isSQLite3File(userFile)
assertTrue(isSqlite3File)
}
}

View File

@ -103,6 +103,15 @@ class JavaUserFile(val path: Path) : UserFile {
override fun resolve(child: String): UserFile {
return JavaUserFile(path.resolve(child))
}
override suspend fun listFiles(): List<UserFile>? {
val files = path.toFile().listFiles() ?: return null
return files.map { JavaUserFile(it.toPath()) }
}
override suspend fun mkdirs() {
path.toFile().mkdirs()
}
}
@Suppress("NewApi")

View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2016-2025 Álinson Santos Xavier <git@axavier.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.isoron.platform.io
import java.io.ByteArrayInputStream
import java.util.zip.ZipInputStream
actual class ZipReader actual constructor(bytes: ByteArray) {
private val data = bytes
actual fun entries(): List<ZipEntry> {
val result = mutableListOf<ZipEntry>()
val zis = ZipInputStream(ByteArrayInputStream(data))
var entry = zis.nextEntry
while (entry != null) {
result.add(ZipEntry(entry.name, zis.readBytes().decodeToString()))
zis.closeEntry()
entry = zis.nextEntry
}
zis.close()
return result
}
}

View File

@ -20,7 +20,6 @@
package org.isoron.platform.io
import java.io.ByteArrayOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
actual class ZipWriter {
@ -28,7 +27,7 @@ actual class ZipWriter {
private val zos = ZipOutputStream(baos)
actual fun addEntry(name: String, content: String) {
zos.putNextEntry(ZipEntry(name))
zos.putNextEntry(java.util.zip.ZipEntry(name))
zos.write(content.toByteArray())
zos.closeEntry()
}

View File

@ -0,0 +1,4 @@
package org.isoron.platform.io
actual fun createTestFileOpener(): FileOpener = JavaFileOpener()
actual fun createTestDatabaseOpener(): DatabaseOpener = JavaDatabaseOpener()

View File

@ -20,7 +20,6 @@ package org.isoron.uhabits.core
import dev.mokkery.spy
import org.apache.commons.io.IOUtils
import org.isoron.platform.io.JavaDatabaseOpener
import org.isoron.uhabits.core.models.memory.MemoryModelFactory
import org.isoron.uhabits.core.test.HabitFixtures
import org.junit.Before
@ -35,8 +34,6 @@ import java.util.GregorianCalendar
import java.util.TimeZone
open class JvmBaseUnitTest : BaseUnitTest() {
protected var databaseOpener: org.isoron.platform.io.DatabaseOpener = JavaDatabaseOpener()
@Before
override fun setUp() {
super.setUp()
@ -75,13 +72,4 @@ open class JvmBaseUnitTest : BaseUnitTest() {
if (inputStream != null) return inputStream
throw IllegalStateException("asset not found: $fullPath")
}
@Throws(IOException::class)
protected fun openDatabaseResource(path: String): org.isoron.platform.io.Database {
val original = openAsset(path)
val tmpDbFile = File.createTempFile("database", ".db")
tmpDbFile.deleteOnExit()
IOUtils.copy(original, FileOutputStream(tmpDbFile))
return databaseOpener.open(tmpDbFile.absolutePath)
}
}

View File

@ -1,106 +0,0 @@
/*
* Copyright (C) 2016-2025 Álinson Santos Xavier <git@axavier.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.io
import kotlinx.coroutines.runBlocking
import org.isoron.uhabits.core.JvmBaseUnitTest
import org.isoron.uhabits.core.models.Habit
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.util.*
import java.util.zip.ZipInputStream
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class HabitsCSVExporterTest : JvmBaseUnitTest() {
private lateinit var baseDir: File
@Before
@Throws(Exception::class)
override fun setUp() {
super.setUp()
habitList.add(fixtures.createShortHabit())
habitList.add(fixtures.createEmptyHabit())
baseDir = Files.createTempDirectory("csv").toFile()
baseDir.deleteOnExit()
}
@Test
@Throws(IOException::class)
fun testExportCSV() = runBlocking {
val selected: MutableList<Habit> = LinkedList()
for (h in habitList) selected.add(h)
val exporter = HabitsCSVExporter(habitList, selected)
val bytes = exporter.writeArchive()
assertTrue(bytes.isNotEmpty())
// Extract zip entries to baseDir for comparison
unzip(bytes)
val filesToCheck = arrayOf(
"001 Meditate/Checkmarks.csv",
"001 Meditate/Scores.csv",
"002 Wake up early/Checkmarks.csv",
"002 Wake up early/Scores.csv",
"Checkmarks.csv",
"Habits.csv",
"Scores.csv"
)
for (file in filesToCheck) {
assertPathExists(file)
assertFileAndReferenceAreEqual(file)
}
}
private fun unzip(bytes: ByteArray) {
val zis = ZipInputStream(ByteArrayInputStream(bytes))
var entry = zis.nextEntry
while (entry != null) {
val outFile = File(baseDir, entry.name)
outFile.parentFile?.mkdirs()
outFile.writeBytes(zis.readBytes())
zis.closeEntry()
entry = zis.nextEntry
}
zis.close()
}
private fun assertPathExists(s: String) {
val file = File(baseDir, s)
assertTrue(
String.format("File %s should exist", file.absolutePath)
) { file.exists() }
}
private fun assertFileAndReferenceAreEqual(s: String) {
val assetFilename = String.format("csv_export/%s", s)
val actualFile = File(baseDir, s)
val expectedFile = File.createTempFile("asset", "")
expectedFile.deleteOnExit()
copyAssetToFile(assetFilename, expectedFile)
val actualContents = actualFile.readText()
val expectedContents = expectedFile.readText()
assertEquals(expectedContents, actualContents, "content mismatch for $s")
}
}

View File

@ -1,20 +0,0 @@
package org.isoron.uhabits.core.utils
import kotlinx.coroutines.runBlocking
import org.isoron.platform.io.JavaUserFile
import org.isoron.uhabits.core.JvmBaseUnitTest
import org.junit.Test
import java.io.File
import kotlin.test.assertTrue
class FileExtensionsTest : JvmBaseUnitTest() {
@Test
fun testIsSQLite3File() = runBlocking {
val file = File.createTempFile("asset", "")
copyAssetToFile("loop.db", file)
val userFile = JavaUserFile(file.toPath())
val isSqlite3File = isSQLite3File(userFile)
assertTrue(isSqlite3File)
}
}