Models unit tests and fix sorting of habitgrouplist

pull/2020/head
Dharanish 1 year ago
parent 5ba2001102
commit 4e70c38a7a

@ -0,0 +1,37 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "org.isoron.uhabits",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 20202,
"versionName": "2.2.2",
"outputFile": "uhabits-android-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/uhabits-android-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/uhabits-android-release.dm"
]
}
],
"minSdkVersionForDexing": 28
}

@ -60,8 +60,10 @@ class HabitCardListController @Inject constructor(
return
}
val hgrFrom = adapter.getHabitGroup(from)!!
val hgrTo = adapter.getHabitGroup(to) ?: return
var hgrFrom = adapter.getHabitGroup(from)!!
if (hgrFrom.parent != null) hgrFrom = hgrFrom.parent!!
var hgrTo = adapter.getHabitGroup(to) ?: return
if (hgrTo.parent != null) hgrTo = hgrTo.parent!!
adapter.performReorder(from, to)
behavior.onReorderHabitGroup(hgrFrom, hgrTo)
}

@ -115,7 +115,7 @@ data class HabitGroup(
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Habit) return false
if (other !is HabitGroup) return false
if (color != other.color) return false
if (description != other.description) return false

@ -200,8 +200,6 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
"Name",
"Question",
"Description",
"NumRepetitions",
"Interval",
"Color"
)
val csv = CSVWriter(out)

@ -45,10 +45,6 @@ class MemoryHabitGroupList : HabitGroupList {
primaryOrder = parent.primaryOrder
secondaryOrder = parent.secondaryOrder
parent.observable.addListener { loadFromParent() }
for (hgr in parent.list) {
hgr.habitList.observable.addListener { loadFromParent() }
hgr.observable.notifyListeners()
}
loadFromParent()
}
@ -213,7 +209,8 @@ class MemoryHabitGroupList : HabitGroupList {
list.add(filteredHgr)
}
}
resort()
primaryOrder = parent!!.primaryOrder
secondaryOrder = parent!!.secondaryOrder
}
@Synchronized
@ -221,7 +218,6 @@ class MemoryHabitGroupList : HabitGroupList {
for (hgr in list) {
hgr.habitList.primaryOrder = primaryOrder
hgr.habitList.secondaryOrder = secondaryOrder
hgr.habitList.resort()
}
if (comparator != null) list.sortWith(comparator!!)
observable.notifyListeners()

@ -242,6 +242,8 @@ class MemoryHabitList : HabitList {
checkNotNull(parent)
list.clear()
for (h in parent!!) if (filter.matches(h)) list.add(h)
primaryOrder = parent!!.primaryOrder
secondaryOrder = parent!!.secondaryOrder
resort()
}

@ -146,14 +146,14 @@ class SQLiteHabitGroupList @Inject constructor(private val modelFactory: ModelFa
if (toRecord == null) throw RuntimeException("habit not in database")
if (toRecord.position!! < fromRecord.position!!) {
repository.execSQL(
"update habits set position = position + 1 " +
"update habitgroups set position = position + 1 " +
"where position >= ? and position < ?",
toRecord.position!!,
fromRecord.position!!
)
} else {
repository.execSQL(
"update habits set position = position - 1 " +
"update habitgroups set position = position - 1 " +
"where position > ? and position <= ?",
fromRecord.position!!,
toRecord.position!!

@ -0,0 +1,266 @@
/*
* Copyright (C) 2016-2021 Á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.models
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.CoreMatchers.not
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest
import org.junit.Assert.assertThrows
import org.junit.Test
import java.io.IOException
import java.io.StringWriter
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
class HabitGroupListTest : BaseUnitTest() {
private lateinit var habitGroupArray: ArrayList<HabitGroup>
private lateinit var activeHabitGroups: HabitGroupList
private lateinit var reminderHabitGroups: HabitGroupList
@Throws(Exception::class)
override fun setUp() {
super.setUp()
habitGroupArray = ArrayList()
for (i in 0..9) {
val hgr = groupFixtures.createEmptyHabitGroup()
habitGroupList.add(hgr)
habitGroupArray.add(hgr)
if (i % 3 == 0) hgr.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY)
}
habitGroupArray[0].isArchived = true
habitGroupArray[1].isArchived = true
habitGroupArray[4].isArchived = true
habitGroupArray[7].isArchived = true
activeHabitGroups = habitGroupList.getFiltered(HabitMatcher())
reminderHabitGroups = habitGroupList.getFiltered(
HabitMatcher(
isArchivedAllowed = true,
isReminderRequired = true
)
)
}
@Test
fun testSize() {
assertThat(habitGroupList.size(), equalTo(10))
assertThat(activeHabitGroups.size(), equalTo(6))
assertThat(reminderHabitGroups.size(), equalTo(4))
}
@Test
fun testGetByPosition() {
assertThat(habitGroupList.getByPosition(0), equalTo(habitGroupArray[0]))
assertThat(habitGroupList.getByPosition(3), equalTo(habitGroupArray[3]))
assertThat(habitGroupList.getByPosition(9), equalTo(habitGroupArray[9]))
assertThat(activeHabitGroups.getByPosition(0), equalTo(habitGroupArray[2]))
assertThat(reminderHabitGroups.getByPosition(1), equalTo(habitGroupArray[3]))
}
@Test
fun testGetById() {
val hgr1 = habitGroupArray[0]
val hgr2 = habitGroupList.getById(hgr1.id!!)
assertThat(hgr1, equalTo(hgr2))
}
@Test
fun testGetById_withInvalidId() {
assertNull(habitGroupList.getById(100L))
}
@Test
fun testOrdering() {
val hgr1 = groupFixtures.createEmptyHabitGroup("A Habit Group", PaletteColor(2), 1)
val hgr2 = groupFixtures.createEmptyHabitGroup("B Habit Group", PaletteColor(2), 3)
val hgr3 = groupFixtures.createEmptyHabitGroup("C Habit Group", PaletteColor(0), 0)
val hgr4 = groupFixtures.createEmptyHabitGroup("D Habit Group", PaletteColor(1), 2)
val list = modelFactory.buildHabitGroupList().apply {
add(hgr3)
add(hgr1)
add(hgr4)
add(hgr2)
}
list.primaryOrder = HabitList.Order.BY_POSITION
assertThat(list.getByPosition(0), equalTo(hgr3))
assertThat(list.getByPosition(1), equalTo(hgr1))
assertThat(list.getByPosition(2), equalTo(hgr4))
assertThat(list.getByPosition(3), equalTo(hgr2))
list.primaryOrder = HabitList.Order.BY_NAME_DESC
assertThat(list.getByPosition(0), equalTo(hgr4))
assertThat(list.getByPosition(1), equalTo(hgr3))
assertThat(list.getByPosition(2), equalTo(hgr2))
assertThat(list.getByPosition(3), equalTo(hgr1))
list.primaryOrder = HabitList.Order.BY_NAME_ASC
assertThat(list.getByPosition(0), equalTo(hgr1))
assertThat(list.getByPosition(1), equalTo(hgr2))
assertThat(list.getByPosition(2), equalTo(hgr3))
assertThat(list.getByPosition(3), equalTo(hgr4))
list.primaryOrder = HabitList.Order.BY_NAME_ASC
list.remove(hgr1)
list.add(hgr1)
assertThat(list.getByPosition(0), equalTo(hgr1))
list.primaryOrder = HabitList.Order.BY_COLOR_ASC
list.secondaryOrder = HabitList.Order.BY_NAME_ASC
assertThat(list.getByPosition(0), equalTo(hgr3))
assertThat(list.getByPosition(1), equalTo(hgr4))
assertThat(list.getByPosition(2), equalTo(hgr1))
assertThat(list.getByPosition(3), equalTo(hgr2))
list.primaryOrder = HabitList.Order.BY_COLOR_DESC
list.secondaryOrder = HabitList.Order.BY_NAME_ASC
assertThat(list.getByPosition(0), equalTo(hgr1))
assertThat(list.getByPosition(1), equalTo(hgr2))
assertThat(list.getByPosition(2), equalTo(hgr4))
assertThat(list.getByPosition(3), equalTo(hgr3))
list.primaryOrder = HabitList.Order.BY_POSITION
assertThat(list.getByPosition(0), equalTo(hgr3))
assertThat(list.getByPosition(1), equalTo(hgr1))
assertThat(list.getByPosition(2), equalTo(hgr4))
assertThat(list.getByPosition(3), equalTo(hgr2))
}
@Test
fun testReorder() {
val operations =
arrayOf(intArrayOf(5, 2), intArrayOf(3, 7), intArrayOf(4, 4), intArrayOf(8, 3))
val expectedSequence = arrayOf(
intArrayOf(0, 1, 5, 2, 3, 4, 6, 7, 8, 9),
intArrayOf(0, 1, 5, 2, 4, 6, 7, 3, 8, 9),
intArrayOf(0, 1, 5, 2, 4, 6, 7, 3, 8, 9),
intArrayOf(0, 1, 5, 2, 4, 6, 7, 8, 3, 9)
)
for (i in operations.indices) {
val fromHabitGroup = habitGroupArray[operations[i][0]]
val toHabitGroup = habitGroupArray[operations[i][1]]
habitGroupList.reorder(fromHabitGroup, toHabitGroup)
val actualSequence = IntArray(10)
for (j in 0..9) {
val hgr = habitGroupList.getByPosition(j)
assertThat(hgr.position, equalTo(j))
actualSequence[j] = Math.toIntExact(hgr.id!!)
}
assertThat(actualSequence, equalTo(expectedSequence[i]))
}
assertThat(activeHabitGroups.indexOf(habitGroupArray[5]), equalTo(0))
assertThat(activeHabitGroups.indexOf(habitGroupArray[2]), equalTo(1))
}
@Test
@Throws(Exception::class)
fun testReorder_withInvalidArguments() {
val hgr1 = habitGroupArray[0]
val hgr2 = groupFixtures.createEmptyHabitGroup()
assertThrows(IllegalArgumentException::class.java) {
habitGroupList.reorder(hgr1, hgr2)
}
}
@Test
fun testOrder_inherit() {
habitGroupList.primaryOrder = HabitList.Order.BY_COLOR_ASC
val filteredList = habitGroupList.getFiltered(
HabitMatcher(
isArchivedAllowed = false,
isCompletedAllowed = false
)
)
assertEquals(filteredList.primaryOrder, HabitList.Order.BY_COLOR_ASC)
}
@Test
@Throws(IOException::class)
fun testWriteCSV() {
val list = modelFactory.buildHabitGroupList()
val hgr1 = groupFixtures.createEmptyHabitGroup()
hgr1.name = "Meditate"
hgr1.question = "Did you meditate this morning?"
hgr1.description = "this is a test description"
hgr1.color = PaletteColor(3)
val hgr2 = groupFixtures.createEmptyHabitGroup()
hgr2.name = "Wake up early"
hgr2.question = "Did you wake up before 6am?"
hgr2.description = ""
hgr2.color = PaletteColor(5)
list.add(hgr1)
list.add(hgr2)
val expectedCSV =
"""
Position,Name,Question,Description,Color
001,Meditate,Did you meditate this morning?,this is a test description,#FF8F00
002,Wake up early,Did you wake up before 6am?,,#AFB42B
""".trimIndent()
val writer = StringWriter()
list.writeCSV(writer)
assertThat(writer.toString(), equalTo(expectedCSV))
}
@Test
@Throws(Exception::class)
fun testAdd() {
val hgr1 = groupFixtures.createEmptyHabitGroup()
assertFalse(hgr1.isArchived)
assertNull(hgr1.id)
assertThat(habitGroupList.indexOf(hgr1), equalTo(-1))
habitGroupList.add(hgr1)
hgr1.id!!
assertThat(habitGroupList.indexOf(hgr1), not(equalTo(-1)))
assertThat(activeHabitGroups.indexOf(hgr1), not(equalTo(-1)))
}
@Test
@Throws(Exception::class)
fun testAdd_withFilteredList() {
assertThrows(IllegalStateException::class.java) {
activeHabitGroups.add(groupFixtures.createEmptyHabitGroup())
}
}
@Test
@Throws(Exception::class)
fun testRemove_onFilteredList() {
assertThrows(IllegalStateException::class.java) {
activeHabitGroups.remove(groupFixtures.createEmptyHabitGroup())
}
}
@Test
@Throws(Exception::class)
fun testReorder_onFilteredList() {
val hgr1 = groupFixtures.createEmptyHabitGroup()
val hgr2 = groupFixtures.createEmptyHabitGroup()
assertThrows(IllegalStateException::class.java) {
activeHabitGroups.reorder(hgr1, hgr2)
}
}
@Test
@Throws(Exception::class)
fun testReorder_onSortedList() {
habitGroupList.primaryOrder = HabitList.Order.BY_SCORE_DESC
val hgr1 = habitGroupArray[1]
val hgr2 = habitGroupArray[2]
assertThrows(IllegalStateException::class.java) {
habitGroupList.reorder(hgr1, hgr2)
}
}
}

@ -121,4 +121,31 @@ class HabitGroupTest : BaseUnitTest() {
assertThat(hgr.id, equalTo(0L))
assertThat(hgr.uriString, equalTo("content://org.isoron.uhabits/habitgroup/0"))
}
@Test
@Throws(Exception::class)
fun testScores() {
val hgr = groupFixtures.createGroupWithNumericalHabits(numHabits = 2)
hgr.recompute()
val today = getToday()
val expectedScore = hgr.habitList.map { it.scores[today].value }.average()
assertEquals(expectedScore, hgr.scores[today].value)
}
@Test
@Throws(Exception::class)
fun testStreaks() {
val hgr = groupFixtures.createGroupWithNumericalHabits(numHabits = 2)
hgr.recompute()
assertEquals(hgr.habitList.getByPosition(0).streaks.getBest(1), hgr.streaks.getBest(1))
val hgr2 = groupFixtures.createGroupWithEmptyHabits(numHabits = 2)
val h = hgr2.habitList.getByPosition(0)
h.originalEntries.add(Entry(getToday(), 2))
h.originalEntries.add(Entry(getToday().minus(1), 2))
val h2 = hgr2.habitList.getByPosition(1)
h2.originalEntries.add(Entry(getToday().minus(2), 2))
hgr2.recompute()
assertEquals(0, hgr2.streaks.getBest(1).size)
}
}

@ -0,0 +1,188 @@
/*
* Copyright (C) 2016-2021 Á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.models.sqlite
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.database.Database
import org.isoron.uhabits.core.database.Repository
import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.HabitGroupList
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.HabitMatcher
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.models.sqlite.records.HabitGroupRecord
import org.isoron.uhabits.core.test.HabitGroupFixtures
import org.junit.Assert.assertThrows
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import kotlin.test.assertNull
class SQLiteHabitGroupListTest : BaseUnitTest() {
private lateinit var repository: Repository<HabitGroupRecord>
private var listener: ModelObservable.Listener = mock()
private lateinit var habitGroupsArray: ArrayList<HabitGroup>
private lateinit var activeHabitGroups: HabitGroupList
private lateinit var reminderHabitGroups: HabitGroupList
@Throws(Exception::class)
override fun setUp() {
super.setUp()
val db: Database = buildMemoryDatabase()
modelFactory = SQLModelFactory(db)
habitGroupList = SQLiteHabitGroupList(modelFactory)
groupFixtures = HabitGroupFixtures(modelFactory, habitList, habitGroupList)
repository = Repository(HabitGroupRecord::class.java, db)
habitGroupsArray = ArrayList()
for (i in 0..9) {
val hgr = groupFixtures.createEmptyHabitGroup()
hgr.name = "habit group " + (i + 1)
habitGroupList.update(hgr)
habitGroupsArray.add(hgr)
if (i % 3 == 0) hgr.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY)
}
habitGroupsArray[0].isArchived = true
habitGroupsArray[1].isArchived = true
habitGroupsArray[4].isArchived = true
habitGroupsArray[7].isArchived = true
habitGroupList.update(habitGroupsArray)
activeHabitGroups = habitGroupList.getFiltered(HabitMatcher())
reminderHabitGroups = habitGroupList.getFiltered(
HabitMatcher(
isArchivedAllowed = true,
isReminderRequired = true
)
)
habitGroupList.observable.addListener(listener)
}
@Throws(Exception::class)
override fun tearDown() {
habitGroupList.observable.removeListener(listener)
super.tearDown()
}
@Test
fun testAdd_withDuplicate() {
val hgr = modelFactory.buildHabitGroup()
habitGroupList.add(hgr)
verify(listener).onModelChange()
assertThrows(IllegalArgumentException::class.java) {
habitGroupList.add(hgr)
}
}
@Test
fun testAdd_withId() {
val hgr = modelFactory.buildHabitGroup()
hgr.name = "Hello world with id"
hgr.id = 12300L
habitGroupList.add(hgr)
assertThat(hgr.id, equalTo(11L))
val record = repository.find(11L)
assertThat(record!!.name, equalTo(hgr.name))
}
@Test
fun testAdd_withoutId() {
val hgr = modelFactory.buildHabitGroup()
hgr.name = "Hello world"
assertNull(hgr.id)
habitGroupList.add(hgr)
val record = repository.find(hgr.id!!)
assertThat(record!!.name, equalTo(hgr.name))
}
@Test
fun testSize() {
assertThat(habitGroupList.size(), equalTo(10))
}
@Test
fun testGetById() {
val hgr1 = habitGroupList.getById(1)!!
assertThat(hgr1.name, equalTo("habit group 1"))
val hgr2 = habitGroupList.getById(2)!!
assertThat(hgr2, equalTo(hgr2))
}
@Test
fun testGetById_withInvalid() {
val invalidId = 9183792001L
val hgr1 = habitGroupList.getById(invalidId)
assertNull(hgr1)
}
@Test
fun testGetByPosition() {
val hgr = habitGroupList.getByPosition(4)
assertThat(hgr.name, equalTo("habit group 5"))
}
@Test
fun testIndexOf() {
val hgr1 = habitGroupList.getByPosition(5)
assertThat(habitGroupList.indexOf(hgr1), equalTo(5))
val hgr2 = modelFactory.buildHabitGroup()
assertThat(habitGroupList.indexOf(hgr2), equalTo(-1))
hgr2.id = 1000L
assertThat(habitGroupList.indexOf(hgr2), equalTo(-1))
}
@Test
@Throws(Exception::class)
fun testRemove() {
val hgr = habitGroupList.getById(2)
habitGroupList.remove(hgr!!)
assertThat(habitGroupList.indexOf(hgr), equalTo(-1))
var rec = repository.find(2L)
assertNull(rec)
rec = repository.find(3L)!!
assertThat(rec.position, equalTo(1))
}
@Test
fun testRemove_orderByName() {
habitGroupList.primaryOrder = HabitList.Order.BY_NAME_DESC
val hgr = habitGroupList.getById(2)
habitGroupList.remove(hgr!!)
assertThat(habitGroupList.indexOf(hgr), equalTo(-1))
var rec = repository.find(2L)
assertNull(rec)
rec = repository.find(3L)!!
assertThat(rec.position, equalTo(1))
}
@Test
fun testReorder() {
val hgr3 = habitGroupList.getById(3)!!
val hgr4 = habitGroupList.getById(4)!!
habitGroupList.reorder(hgr4, hgr3)
val record3 = repository.find(3L)!!
assertThat(record3.position, equalTo(3))
val record4 = repository.find(4L)!!
assertThat(record4.position, equalTo(2))
}
}

@ -0,0 +1,65 @@
/*
* Copyright (C) 2016-2021 Á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.models.sqlite.records
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.WeekdayList
import org.junit.Test
class HabitGroupRecordTest : BaseUnitTest() {
@Test
fun testCopyRestore1() {
val original = modelFactory.buildHabitGroup().apply {
name = "Hello world"
question = "Did you greet the world today?"
color = PaletteColor(1)
isArchived = true
reminder = Reminder(8, 30, WeekdayList.EVERY_DAY)
id = 1000L
position = 20
}
val record = HabitGroupRecord()
record.copyFrom(original)
val duplicate = modelFactory.buildHabitGroup()
record.copyTo(duplicate)
assertThat(original, equalTo(duplicate))
}
@Test
fun testCopyRestore2() {
val original = modelFactory.buildHabitGroup().apply {
name = "Hello world"
question = "Did you greet the world today?"
color = PaletteColor(5)
isArchived = false
reminder = null
id = 1L
position = 15
}
val record = HabitGroupRecord()
record.copyFrom(original)
val duplicate = modelFactory.buildHabitGroup()
record.copyTo(duplicate)
assertThat(original, equalTo(duplicate))
}
}
Loading…
Cancel
Save