Issue 1316: Skip measurable habit (#1319)

Co-authored-by: Jakub Kalinowski <kalj@netcompany.com>
pull/1336/head
Jakub Kalinowski 4 years ago committed by GitHub
parent ca4618579e
commit 3b12ec4bfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,6 +22,7 @@ package org.isoron.uhabits.activities.common.dialogs
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.DialogInterface.BUTTON_NEGATIVE
import android.text.InputFilter import android.text.InputFilter
import android.text.Spanned import android.text.Spanned
import android.view.LayoutInflater import android.view.LayoutInflater
@ -33,7 +34,11 @@ import android.widget.EditText
import android.widget.NumberPicker import android.widget.NumberPicker
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Frequency.Companion.DAILY
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.inject.ActivityContext import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.InterfaceUtils import org.isoron.uhabits.utils.InterfaceUtils
@ -52,6 +57,7 @@ class NumberPickerFactory
unit: String, unit: String,
notes: String, notes: String,
dateString: String, dateString: String,
frequency: Frequency,
callback: ListHabitsBehavior.NumberPickerCallback callback: ListHabitsBehavior.NumberPickerCallback
): AlertDialog { ): AlertDialog {
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(context)
@ -92,7 +98,7 @@ class NumberPickerFactory
picker2.value = intValue % 100 picker2.value = intValue % 100
etNotes.setText(notes) etNotes.setText(notes)
val dialog = AlertDialog.Builder(context) val dialogBuilder = AlertDialog.Builder(context)
.setView(view) .setView(view)
.setTitle(dateString) .setTitle(dateString)
.setPositiveButton(R.string.save) { _, _ -> .setPositiveButton(R.string.save) { _, _ ->
@ -108,9 +114,24 @@ class NumberPickerFactory
.setOnDismissListener { .setOnDismissListener {
callback.onNumberPickerDismissed() callback.onNumberPickerDismissed()
} }
.create()
if (frequency == DAILY) {
dialogBuilder.setNegativeButton(R.string.skip_day) { _, _ ->
picker.clearFocus()
val v = Entry.SKIP.toDouble() / 1000
val note = etNotes.text.toString()
callback.onNumberPicked(v, note)
}
}
val dialog = dialogBuilder.create()
dialog.setOnShowListener { dialog.setOnShowListener {
val preferences =
(context.applicationContext as HabitsApplication).component.preferences
if (!preferences.isSkipEnabled) {
dialog.getButton(BUTTON_NEGATIVE).visibility = View.GONE
}
showSoftInput(dialog, pickerInputText) showSoftInput(dialog, pickerInputText)
} }

@ -173,7 +173,8 @@ class FrequencyChart : ScrollableChart {
rect[0f, 0f, baseSize.toFloat()] = baseSize.toFloat() rect[0f, 0f, baseSize.toFloat()] = baseSize.toFloat()
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j) rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
val i = localeWeekdayList[j] % 7 val i = localeWeekdayList[j] % 7
if (values != null) drawMarker(canvas, rect, values[i]) if (values != null)
drawMarker(canvas, rect, values[i])
rect.offset(0f, rowHeight) rect.offset(0f, rowHeight)
} }
drawFooter(canvas, rect, date) drawFooter(canvas, rect, date)
@ -222,11 +223,14 @@ class FrequencyChart : ScrollableChart {
} }
private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?) { private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?) {
// value can be negative when the entry is skipped
val valueCopy = value?.let { max(0, it) }
val padding = rect!!.height() * 0.2f val padding = rect!!.height() * 0.2f
// maximal allowed mark radius // maximal allowed mark radius
val maxRadius = (rect.height() - 2 * padding) / 2.0f val maxRadius = (rect.height() - 2 * padding) / 2.0f
// the real mark radius is scaled down by a factor depending on the maximal frequency // the real mark radius is scaled down by a factor depending on the maximal frequency
val scale = 1.0f / maxFreq * value!! val scale = 1.0f / maxFreq * valueCopy!!
val radius = maxRadius * scale val radius = maxRadius * scale
val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt()) val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt())
pGraph!!.color = colors[colorIndex] pGraph!!.color = colors[colorIndex]

@ -40,6 +40,7 @@ import org.isoron.uhabits.core.commands.CreateHabitCommand
import org.isoron.uhabits.core.commands.DeleteHabitsCommand import org.isoron.uhabits.core.commands.DeleteHabitsCommand
import org.isoron.uhabits.core.commands.EditHabitCommand import org.isoron.uhabits.core.commands.EditHabitCommand
import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.tasks.TaskRunner import org.isoron.uhabits.core.tasks.TaskRunner
@ -230,9 +231,10 @@ class ListHabitsScreen
unit: String, unit: String,
notes: String, notes: String,
dateString: String, dateString: String,
frequency: Frequency,
callback: ListHabitsBehavior.NumberPickerCallback callback: ListHabitsBehavior.NumberPickerCallback
) { ) {
numberPickerFactory.create(value, unit, notes, dateString, callback).show() numberPickerFactory.create(value, unit, notes, dateString, frequency, callback).show()
} }
override fun showCheckmarkDialog( override fun showCheckmarkDialog(

@ -29,6 +29,7 @@ import android.view.View
import android.view.View.OnClickListener import android.view.View.OnClickListener
import android.view.View.OnLongClickListener import android.view.View.OnLongClickListener
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST
import org.isoron.uhabits.core.models.NumericalHabitType.AT_MOST import org.isoron.uhabits.core.models.NumericalHabitType.AT_MOST
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
@ -143,6 +144,12 @@ class NumberButtonView(
private val lowContrast: Int private val lowContrast: Int
private val mediumContrast: Int private val mediumContrast: Int
private val paint = TextPaint().apply {
typeface = getFontAwesome()
isAntiAlias = true
textAlign = Paint.Align.CENTER
}
private val pUnit: TextPaint = TextPaint().apply { private val pUnit: TextPaint = TextPaint().apply {
textSize = getDimension(context, R.dimen.smallerTextSize) textSize = getDimension(context, R.dimen.smallerTextSize)
typeface = NORMAL_TYPEFACE typeface = NORMAL_TYPEFACE
@ -176,6 +183,11 @@ class NumberButtonView(
val textSize: Float val textSize: Float
when { when {
value == Entry.SKIP.toDouble() / 1000 -> {
label = resources.getString(R.string.fa_skipped)
textSize = dim(R.dimen.smallTextSize)
typeface = getFontAwesome()
}
value >= 0 -> { value >= 0 -> {
label = value.toShortString() label = value.toShortString()
typeface = BOLD_TYPEFACE typeface = BOLD_TYPEFACE

@ -39,6 +39,7 @@ import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
import org.isoron.uhabits.core.commands.Command import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
@ -169,9 +170,10 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
unit: String, unit: String,
notes: String, notes: String,
dateString: String, dateString: String,
callback: ListHabitsBehavior.NumberPickerCallback, frequency: Frequency,
callback: ListHabitsBehavior.NumberPickerCallback
) { ) {
NumberPickerFactory(this@ShowHabitActivity).create(value, unit, notes, dateString, callback).show() NumberPickerFactory(this@ShowHabitActivity).create(value, unit, notes, dateString, frequency, callback).show()
} }
override fun showCheckmarkDialog( override fun showCheckmarkDialog(

@ -81,6 +81,7 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi
data.habit.unit, data.habit.unit,
entry.notes, entry.notes,
today.toDialogDateString(), today.toDialogDateString(),
data.habit.frequency,
this this
).show() ).show()
} }

@ -225,6 +225,7 @@
<string name="increment">Increment</string> <string name="increment">Increment</string>
<string name="decrement">Decrement</string> <string name="decrement">Decrement</string>
<string name="pref_skip_title">Enable skip days</string> <string name="pref_skip_title">Enable skip days</string>
<string name="skip_day">Skip</string>
<string name="pref_skip_description">Toggle twice to add a skip instead of a checkmark. Skips keep your score unchanged and don\'t break your streak.</string> <string name="pref_skip_description">Toggle twice to add a skip instead of a checkmark. Skips keep your score unchanged and don\'t break your streak.</string>
<string name="pref_unknown_title">Show question marks for missing data</string> <string name="pref_unknown_title">Show question marks for missing data</string>
<string name="pref_unknown_description">Differentiate days without data from actual lapses. To enter a lapse, toggle twice.</string> <string name="pref_unknown_description">Differentiate days without data from actual lapses. To enter a lapse, toggle twice.</string>

@ -276,6 +276,8 @@ open class EntryList {
* For numerical habits, non-positive entry values are converted to zero. For boolean habits, each * For numerical habits, non-positive entry values are converted to zero. For boolean habits, each
* YES_MANUAL value is converted to 1000 and all other values are converted to zero. * YES_MANUAL value is converted to 1000 and all other values are converted to zero.
* *
* SKIP values are converted to zero (if they weren't, each SKIP day would count as 0.003).
*
* The returned list is sorted by timestamp, with the newest entry coming first and the oldest entry * The returned list is sorted by timestamp, with the newest entry coming first and the oldest entry
* coming last. If the original list has gaps in it (for example, weeks or months without any * coming last. If the original list has gaps in it (for example, weeks or months without any
* entries), then the list produced by this method will also have gaps. * entries), then the list produced by this method will also have gaps.
@ -289,7 +291,10 @@ fun List<Entry>.groupedSum(
): List<Entry> { ): List<Entry> {
return this.map { (timestamp, value) -> return this.map { (timestamp, value) ->
if (isNumerical) { if (isNumerical) {
Entry(timestamp, max(0, value)) if (value == SKIP)
Entry(timestamp, 0)
else
Entry(timestamp, max(0, value))
} else { } else {
Entry(timestamp, if (value == YES_MANUAL) 1000 else 0) Entry(timestamp, if (value == YES_MANUAL) 1000 else 0)
} }
@ -301,6 +306,31 @@ fun List<Entry>.groupedSum(
}.entries.map { (timestamp, entries) -> }.entries.map { (timestamp, entries) ->
Entry(timestamp, entries.sumOf { it.value }) Entry(timestamp, entries.sumOf { it.value })
}.sortedBy { (timestamp, _) -> }.sortedBy { (timestamp, _) ->
- timestamp.unixTime -timestamp.unixTime
}
}
/**
* Counts the number of days with vaLue SKIP in the given period.
*/
fun List<Entry>.countSkippedDays(
truncateField: DateUtils.TruncateField,
firstWeekday: Int = Calendar.SATURDAY
): List<Entry> {
return this.map { (timestamp, value) ->
if (value == SKIP) {
Entry(timestamp, 1)
} else {
Entry(timestamp, 0)
}
}.groupBy { entry ->
entry.timestamp.truncate(
truncateField,
firstWeekday,
)
}.entries.map { (timestamp, entries) ->
Entry(timestamp, entries.sumOf { it.value })
}.sortedBy { (timestamp, _) ->
-timestamp.unixTime
} }
} }

@ -100,20 +100,25 @@ class ScoreList {
} }
val normalizedRollingSum = rollingSum / 1000 val normalizedRollingSum = rollingSum / 1000
val percentageCompleted = if (!isAtMost) { if (values[offset] != Entry.SKIP) {
if (targetValue > 0) val percentageCompleted = if (!isAtMost) {
min(1.0, normalizedRollingSum / targetValue) if (targetValue > 0)
else min(1.0, normalizedRollingSum / targetValue)
1.0 else
} else { 1.0
if (targetValue > 0) {
(1 - ((normalizedRollingSum - targetValue) / targetValue)).coerceIn(0.0, 1.0)
} else { } else {
if (normalizedRollingSum > 0) 0.0 else 1.0 if (targetValue > 0) {
(1 - ((normalizedRollingSum - targetValue) / targetValue)).coerceIn(
0.0,
1.0
)
} else {
if (normalizedRollingSum > 0) 0.0 else 1.0
}
} }
}
previousValue = compute(freq, previousValue, percentageCompleted) previousValue = compute(freq, previousValue, percentageCompleted)
}
} else { } else {
if (values[offset] == Entry.YES_MANUAL) { if (values[offset] == Entry.YES_MANUAL) {
rollingSum += 1.0 rollingSum += 1.0

@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.list
import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDate
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.HabitType import org.isoron.uhabits.core.models.HabitType
@ -58,6 +59,7 @@ open class ListHabitsBehavior @Inject constructor(
habit.unit, habit.unit,
entry.notes, entry.notes,
timestamp.toDialogDateString(), timestamp.toDialogDateString(),
habit.frequency
) { newValue: Double, newNotes: String, -> ) { newValue: Double, newNotes: String, ->
val value = (newValue * 1000).roundToInt() val value = (newValue * 1000).roundToInt()
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes)) commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes))
@ -167,6 +169,7 @@ open class ListHabitsBehavior @Inject constructor(
unit: String, unit: String,
notes: String, notes: String,
dateString: String, dateString: String,
frequency: Frequency,
callback: NumberPickerCallback callback: NumberPickerCallback
) )
fun showCheckmarkDialog( fun showCheckmarkDialog(

@ -27,6 +27,7 @@ import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Entry.Companion.SKIP import org.isoron.uhabits.core.models.Entry.Companion.SKIP
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST
@ -123,6 +124,7 @@ class HistoryCardPresenter(
habit.unit, habit.unit,
entry.notes, entry.notes,
timestamp.toDialogDateString(), timestamp.toDialogDateString(),
frequency = habit.frequency
) { newValue: Double, newNotes: String -> ) { newValue: Double, newNotes: String ->
val thousands = (newValue * 1000).roundToInt() val thousands = (newValue * 1000).roundToInt()
commandRunner.run( commandRunner.run(
@ -154,6 +156,7 @@ class HistoryCardPresenter(
entries.map { entries.map {
when { when {
it.value == Entry.UNKNOWN -> OFF it.value == Entry.UNKNOWN -> OFF
it.value == SKIP -> HATCHED
(habit.targetType == AT_MOST) && (it.value / 1000.0 <= habit.targetValue) -> ON (habit.targetType == AT_MOST) && (it.value / 1000.0 <= habit.targetValue) -> ON
(habit.targetType == AT_LEAST) && (it.value / 1000.0 >= habit.targetValue) -> ON (habit.targetType == AT_LEAST) && (it.value / 1000.0 >= habit.targetValue) -> ON
else -> GREY else -> GREY
@ -196,8 +199,10 @@ class HistoryCardPresenter(
unit: String, unit: String,
notes: String, notes: String,
dateString: String, dateString: String,
callback: ListHabitsBehavior.NumberPickerCallback, frequency: Frequency,
callback: ListHabitsBehavior.NumberPickerCallback
) )
fun showCheckmarkDialog( fun showCheckmarkDialog(
selectedValue: Int, selectedValue: Int,
notes: String, notes: String,

@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.countSkippedDays
import org.isoron.uhabits.core.models.groupedSum import org.isoron.uhabits.core.models.groupedSum
import org.isoron.uhabits.core.ui.views.Theme import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
@ -51,37 +52,59 @@ class TargetCardPresenter {
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDayToday = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.DAY
).firstOrNull()?.value ?: 0
val valueThisWeek = entries.groupedSum( val valueThisWeek = entries.groupedSum(
truncateField = DateUtils.TruncateField.WEEK_NUMBER, truncateField = DateUtils.TruncateField.WEEK_NUMBER,
firstWeekday = firstWeekday, firstWeekday = firstWeekday,
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDaysThisWeek = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.WEEK_NUMBER,
firstWeekday = firstWeekday
).firstOrNull()?.value ?: 0
val valueThisMonth = entries.groupedSum( val valueThisMonth = entries.groupedSum(
truncateField = DateUtils.TruncateField.MONTH, truncateField = DateUtils.TruncateField.MONTH,
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDaysThisMonth = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.MONTH,
).firstOrNull()?.value ?: 0
val valueThisQuarter = entries.groupedSum( val valueThisQuarter = entries.groupedSum(
truncateField = DateUtils.TruncateField.QUARTER, truncateField = DateUtils.TruncateField.QUARTER,
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDaysThisQuarter = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.QUARTER
).firstOrNull()?.value ?: 0
val valueThisYear = entries.groupedSum( val valueThisYear = entries.groupedSum(
truncateField = DateUtils.TruncateField.YEAR, truncateField = DateUtils.TruncateField.YEAR,
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDaysThisYear = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.YEAR
).firstOrNull()?.value ?: 0
val cal = DateUtils.getStartOfTodayCalendarWithOffset() val cal = DateUtils.getStartOfTodayCalendarWithOffset()
val daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH) val daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH)
val daysInQuarter = 91 val daysInQuarter = 91
val daysInYear = cal.getActualMaximum(Calendar.DAY_OF_YEAR) val daysInYear = cal.getActualMaximum(Calendar.DAY_OF_YEAR)
val targetToday = habit.targetValue / habit.frequency.denominator val dailyTarget = habit.targetValue / habit.frequency.denominator
val targetThisWeek = targetToday * 7 val targetToday = dailyTarget * (1 - skippedDayToday)
val targetThisMonth = targetToday * daysInMonth val targetThisWeek = dailyTarget * (7 - skippedDaysThisWeek)
val targetThisQuarter = targetToday * daysInQuarter val targetThisMonth = dailyTarget * (daysInMonth - skippedDaysThisMonth)
val targetThisYear = targetToday * daysInYear val targetThisQuarter = dailyTarget * (daysInQuarter - skippedDaysThisQuarter)
val targetThisYear = dailyTarget * (daysInYear - skippedDaysThisYear)
val values = ArrayList<Double>() val values = ArrayList<Double>()
if (habit.frequency.denominator <= 1) values.add(valueToday / 1e3) if (habit.frequency.denominator <= 1) values.add(valueToday / 1e3)

@ -23,6 +23,7 @@ import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.number.IsCloseTo import org.hamcrest.number.IsCloseTo
import org.hamcrest.number.OrderingComparison import org.hamcrest.number.OrderingComparison
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Entry.Companion.SKIP
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -381,6 +382,66 @@ class NumericalAtLeastScoreListTest : NumericalScoreListTest() {
} }
} }
class NumericalAtLeastScoreListWithSkipTest : NumericalScoreListTest() {
@Before
@Throws(Exception::class)
override fun setUp() {
super.setUp()
habit = fixtures.createEmptyNumericalHabit(NumericalHabitType.AT_LEAST)
}
@Test
fun test_getValue() {
addEntries(0, 10, 2000)
addEntries(10, 11, SKIP)
addEntries(11, 15, 2000)
addEntries(15, 16, SKIP)
addEntries(16, 20, 2000)
val expectedValues = doubleArrayOf(
0.617008,
0.596033,
0.573910,
0.550574,
0.525961,
0.500000,
0.472617,
0.443734,
0.413270,
0.381137,
0.347244, // skipped day should have the same score as the previous day
0.347244,
0.311495,
0.273788,
0.234017,
0.192067, // skipped day should have the same score as the previous day
0.192067,
0.147820,
0.101149,
0.051922,
0.000000,
0.000000,
0.000000
)
checkScoreValues(expectedValues)
}
@Test
fun skipsShouldNotAffectScore() {
addEntries(0, 500, 1000)
val initialScore = habit.scores[today].value
addEntries(500, 1000, SKIP)
assertThat(habit.scores[today].value, IsCloseTo.closeTo(initialScore, E))
addEntries(0, 300, 1000)
addEntries(300, 500, SKIP)
addEntries(500, 700, 1000)
// skipped days should be treated as if they never existed
assertThat(habit.scores[today].value, IsCloseTo.closeTo(initialScore, E))
}
}
class NumericalAtMostScoreListTest : NumericalScoreListTest() { class NumericalAtMostScoreListTest : NumericalScoreListTest() {
@Before @Before
@Throws(Exception::class) @Throws(Exception::class)

@ -33,6 +33,7 @@ import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual.equalTo import org.hamcrest.core.IsEqual.equalTo
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Entry 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.Habit
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
@ -79,7 +80,14 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
@Test @Test
fun testOnEdit() { fun testOnEdit() {
behavior.onEdit(habit2, getToday()) behavior.onEdit(habit2, getToday())
verify(screen).showNumberPicker(eq(0.1), eq("miles"), eq(""), eq("Jan 25, 2015"), picker.capture()) verify(screen).showNumberPicker(
eq(0.1),
eq("miles"),
eq(""),
eq("Jan 25, 2015"),
eq(Frequency.DAILY),
picker.capture()
)
picker.lastValue.onNumberPicked(100.0, "") picker.lastValue.onNumberPicked(100.0, "")
val today = getTodayWithOffset() val today = getTodayWithOffset()
assertThat(habit2.computedEntries.get(today).value, equalTo(100000)) assertThat(habit2.computedEntries.get(today).value, equalTo(100000))

Loading…
Cancel
Save