Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,13 @@ open class Preferences(private val storage: Storage) {
storage.putBoolean("pref_short_toggle", enabled)
}

internal fun setActiveNotifications(activeNotifications: Map<Habit, NotificationTray.NotificationData>) {
internal open fun setActiveNotifications(activeNotifications: Map<Habit, NotificationTray.NotificationData>) {
val activeById = activeNotifications.mapKeys { it.key.id }
val serialized = Json.encodeToString(activeById)
storage.putString("pref_active_notifications", serialized)
}

internal fun getActiveNotifications(habitList: HabitList): HashMap<Habit, NotificationTray.NotificationData> {
internal open fun getActiveNotifications(habitList: HabitList): HashMap<Habit, NotificationTray.NotificationData> {
val serialized = storage.getString("pref_active_notifications", "")
return if (serialized == "") {
HashMap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,25 @@ class NotificationTray @Inject constructor(
internal class NotificationData(
val timestamp: Timestamp,
val reminderTime: Long,
)
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as NotificationData

if (timestamp != other.timestamp) return false
if (reminderTime != other.reminderTime) return false

return true
}

override fun hashCode(): Int {
var result = timestamp.hashCode()
result = 31 * result + reminderTime.hashCode()
return result
}
}

private inner class ShowNotificationTask(
private val habit: Habit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import junit.framework.Assert.assertTrue
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.models.Timestamp.Companion.ZERO
import org.isoron.uhabits.core.ui.NotificationTray
import org.isoron.uhabits.core.ui.ThemeSwitcher
import org.junit.Before
import org.junit.Test
Expand Down Expand Up @@ -162,6 +165,31 @@ class PreferencesTest : BaseUnitTest() {
assertFalse(prefs.showCompleted)
}

@Test
@Throws(Exception::class)
fun testActiveNotifications() {
repeat(5) { habitList.add(fixtures.createEmptyHabit()) }

// Initially no active notifications
assertThat(prefs.getActiveNotifications(habitList), equalTo(HashMap()))

// Example map of active notifications
val a = HashMap<Habit, NotificationTray.NotificationData>()
for (i in listOf(0, 1, 3)) {
val habit = habitList.getByPosition(i)
val data = NotificationTray.NotificationData(Timestamp(10000L * i), 200000L * i)
a[habit] = data
}

// Persist and retrieve active notifications
prefs.setActiveNotifications(a)
val b = prefs.getActiveNotifications(habitList)

// Assert that persisted and retrieved maps are teh same
assertThat(a.keys, equalTo(b.keys))
a.forEach { e -> assertThat(b[e.key], equalTo(e.value)) }
Comment on lines +189 to +190
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a manual workaround for the missing matcher/library function I was looking for.

}

@Test
@Throws(Exception::class)
fun testMidnightDelay() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.isoron.uhabits.core.ui

import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.models.WeekdayList
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.preferences.Preferences.Storage
import org.junit.Before
import org.junit.Test

class NotificationTrayTest : BaseUnitTest() {
private val systemTray = object : NotificationTray.SystemTray {
override fun removeNotification(notificationId: Int) {}

override fun showNotification(
habit: Habit,
notificationId: Int,
timestamp: Timestamp,
reminderTime: Long,
silent: Boolean
) {
}

override fun log(msg: String) {}
}

private var preferences = MockPreferences()
private lateinit var notificationTray: NotificationTray

class DummyStorage : Storage {
override fun clear() {
throw NotImplementedError("Mock implementation missing")
}

override fun getBoolean(key: String, defValue: Boolean): Boolean {
throw NotImplementedError("Mock implementation missing")
}

override fun getInt(key: String, defValue: Int): Int {
throw NotImplementedError("Mock implementation missing")
}

override fun getLong(key: String, defValue: Long): Long {
throw NotImplementedError("Mock implementation missing")
}

override fun getString(key: String, defValue: String): String {
throw NotImplementedError("Mock implementation missing")
}

override fun onAttached(preferences: Preferences) {
}

override fun putBoolean(key: String, value: Boolean) {
throw NotImplementedError("Mock implementation missing")
}

override fun putInt(key: String, value: Int) {
throw NotImplementedError("Mock implementation missing")
}

override fun putLong(key: String, value: Long) {
throw NotImplementedError("Mock implementation missing")
}

override fun putString(key: String, value: String) {
throw NotImplementedError("Mock implementation missing")
}

override fun remove(key: String) {
throw NotImplementedError("Mock implementation missing")
}
}

class MockPreferences : Preferences(DummyStorage()) {
private var activeNotifications: HashMap<Habit, NotificationTray.NotificationData> =
HashMap()

override fun setActiveNotifications(activeNotifications: Map<Habit, NotificationTray.NotificationData>) {
this.activeNotifications = HashMap(activeNotifications)
}

override fun getActiveNotifications(habitList: HabitList): HashMap<Habit, NotificationTray.NotificationData> {
return activeNotifications
}
}

@Before
@Throws(Exception::class)
override fun setUp() {
super.setUp()
notificationTray =
NotificationTray(taskRunner, commandRunner, preferences, systemTray, habitList)
}

@Test
@Throws(Exception::class)
fun testShow() {
// Show a reminder for a habit
val habit = fixtures.createEmptyHabit()
habit.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY)
val timestamp = Timestamp(System.currentTimeMillis())
val reminderTime = System.currentTimeMillis()
notificationTray.show(habit, timestamp, reminderTime)

// Verify that the active notifications include exactly the one shown reminder
// TODO are we guaranteed that task has executed?
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a ShowNotificationTask is executed by a task runner, I am not sure whether this might be asynchronous and we should for example add a delay here to increase the likelihood that it has executed? Or are tasks (at least for these tests) run in the same thread, which would make tests safer?

assertThat(preferences.getActiveNotifications(habitList).size, equalTo(1))
assertThat(
preferences.getActiveNotifications(habitList)[habit],
equalTo(NotificationTray.NotificationData(timestamp, reminderTime))
)

// Remove the reminder from the notification tray and verify that active notifications are empty
notificationTray.cancel(habit)
assertThat(preferences.getActiveNotifications(habitList).size, equalTo(0))

// TODO test cases where reminders should be removed (e.g. reshowAll)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we could add tests whether the remaining methods which touch the active notifications map remove entries in the correct situations. It is a little work to setup all the special cases, I can't promise right now when I'll be able to do that.

}
}