mirror of https://github.com/iSoron/uhabits.git
parent
d9be39b839
commit
94c78ebb72
@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
||||||
|
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.utils.*
|
||||||
|
import kotlin.collections.set
|
||||||
|
import kotlin.math.*
|
||||||
|
|
||||||
|
class Entries {
|
||||||
|
|
||||||
|
private val entriesByTimestamp: HashMap<Timestamp, Entry> = HashMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the entry corresponding to the given timestamp. If no entry with such timestamp
|
||||||
|
* has been previously added, returns Entry(timestamp, UNKNOWN).
|
||||||
|
*/
|
||||||
|
fun get(timestamp: Timestamp): Entry {
|
||||||
|
return entriesByTimestamp[timestamp] ?: Entry(timestamp, UNKNOWN)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns one entry for each day in the given interval. The first element corresponds to the
|
||||||
|
* newest entry, and the last element corresponds to the oldest. The interval endpoints are
|
||||||
|
* included.
|
||||||
|
*/
|
||||||
|
fun getByInterval(from: Timestamp, to: Timestamp): List<Entry> {
|
||||||
|
val result = mutableListOf<Entry>()
|
||||||
|
var current = to
|
||||||
|
while (current >= from) {
|
||||||
|
result.add(get(current))
|
||||||
|
current = current.minus(1)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given entry to the list. If another entry with the same timestamp already exists,
|
||||||
|
* replaces it.
|
||||||
|
*/
|
||||||
|
fun add(entry: Entry) {
|
||||||
|
entriesByTimestamp[entry.timestamp] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all entries whose values are known, sorted by timestamp. The first element
|
||||||
|
* corresponds to the newest entry, and the last element corresponds to the oldest.
|
||||||
|
*/
|
||||||
|
fun getKnown(): List<Entry> {
|
||||||
|
return entriesByTimestamp.values.sortedBy { it.timestamp }.reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncates the timestamps of all known entries, then aggregates their values. This function
|
||||||
|
* is used to generate bar plots where each bar shows the number of repetitions in a given week,
|
||||||
|
* month or year.
|
||||||
|
*
|
||||||
|
* For boolean habits, the value of the aggregated entry equals to the number of YES_MANUAL
|
||||||
|
* entries. For numerical habits, the value is the total sum. The field [firstWeekday] is only
|
||||||
|
* relevant when grouping by week.
|
||||||
|
*/
|
||||||
|
fun groupBy(
|
||||||
|
field: DateUtils.TruncateField,
|
||||||
|
firstWeekday: Int,
|
||||||
|
isNumerical: Boolean,
|
||||||
|
): List<Entry> {
|
||||||
|
val original = getKnown()
|
||||||
|
val truncated = original.map {
|
||||||
|
Entry(it.timestamp.truncate(field, firstWeekday), it.value)
|
||||||
|
}
|
||||||
|
val timestamps = mutableListOf<Timestamp>()
|
||||||
|
val values = mutableListOf<Int>()
|
||||||
|
for (i in truncated.indices) {
|
||||||
|
if (i == 0 || timestamps.last() != truncated[i].timestamp) {
|
||||||
|
timestamps.add(truncated[i].timestamp)
|
||||||
|
values.add(0)
|
||||||
|
}
|
||||||
|
if (isNumerical) {
|
||||||
|
values[values.lastIndex] += truncated[i].value
|
||||||
|
} else {
|
||||||
|
if (values[values.lastIndex] == YES_MANUAL) {
|
||||||
|
values[values.lastIndex] += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timestamps.indices.map { Entry(timestamps[it], values[it]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces all entries in this list by entries computed automatically from another list.
|
||||||
|
*
|
||||||
|
* For boolean habits, this function creates additional entries (with value YES_AUTO) according
|
||||||
|
* to the frequency of the habit. For numerical habits, this function simply copies all entries.
|
||||||
|
*/
|
||||||
|
fun computeFrom(
|
||||||
|
other: Entries,
|
||||||
|
frequency: Frequency,
|
||||||
|
isNumerical: Boolean,
|
||||||
|
) {
|
||||||
|
clear()
|
||||||
|
val original = other.getKnown()
|
||||||
|
if (isNumerical) {
|
||||||
|
original.forEach { add(it) }
|
||||||
|
} else {
|
||||||
|
val intervals = buildIntervals(frequency, original)
|
||||||
|
if (intervals.isEmpty()) return
|
||||||
|
snapIntervalsTogether(intervals)
|
||||||
|
val computed = buildEntriesFromInterval(original, intervals)
|
||||||
|
computed.filter { it.value != UNKNOWN }.forEach { add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Interval(val begin: Timestamp, val center: Timestamp, val end: Timestamp) {
|
||||||
|
val length: Int
|
||||||
|
get() = begin.daysUntil(end) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all known entries.
|
||||||
|
*/
|
||||||
|
fun clear() {
|
||||||
|
entriesByTimestamp.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a list of intervals into a list of entries. Entries that fall outside of any
|
||||||
|
* interval receive value UNKNOWN. Entries that fall within an interval but do not appear
|
||||||
|
* in [original] receive value YES_AUTO. Entries provided in [original] are just copied over.
|
||||||
|
*
|
||||||
|
* The intervals should be sorted by timestamp. The first element in the list should
|
||||||
|
* correspond to the newest interval.
|
||||||
|
*/
|
||||||
|
companion object {
|
||||||
|
fun buildEntriesFromInterval(
|
||||||
|
original: List<Entry>,
|
||||||
|
intervals: List<Interval>,
|
||||||
|
): List<Entry> {
|
||||||
|
val toTimestamp = intervals.first().end
|
||||||
|
val fromTimstamp = intervals.last().begin
|
||||||
|
val result = arrayListOf<Entry>()
|
||||||
|
|
||||||
|
// Create unknown entries
|
||||||
|
var current = toTimestamp
|
||||||
|
while (current >= fromTimstamp) {
|
||||||
|
result.add(Entry(current, UNKNOWN))
|
||||||
|
current = current.minus(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create YES_AUTO entries
|
||||||
|
intervals.forEach { interval ->
|
||||||
|
current = interval.end
|
||||||
|
while (current >= interval.begin) {
|
||||||
|
val offset = current.daysUntil(toTimestamp)
|
||||||
|
result[offset] = Entry(current, YES_AUTO)
|
||||||
|
current = current.minus(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy original entries
|
||||||
|
original.forEach { entry ->
|
||||||
|
val offset = entry.timestamp.daysUntil(toTimestamp)
|
||||||
|
result[offset] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starting from the second newest interval, this function tries to slide the
|
||||||
|
* intervals backwards into the past, so that gaps are eliminated and
|
||||||
|
* streaks are maximized.
|
||||||
|
*
|
||||||
|
* The intervals should be sorted by timestamp. The first element in the list should
|
||||||
|
* correspond to the newest interval.
|
||||||
|
*/
|
||||||
|
fun snapIntervalsTogether(intervals: ArrayList<Interval>) {
|
||||||
|
for (i in 1 until intervals.size) {
|
||||||
|
val curr = intervals[i]
|
||||||
|
val next = intervals[i - 1]
|
||||||
|
val gapNextToCurrent = next.begin.daysUntil(curr.end)
|
||||||
|
val gapCenterToEnd = curr.center.daysUntil(curr.end)
|
||||||
|
if (gapNextToCurrent >= 0) {
|
||||||
|
val shift = min(gapCenterToEnd, gapNextToCurrent + 1)
|
||||||
|
intervals[i] = Interval(curr.begin.minus(shift),
|
||||||
|
curr.center,
|
||||||
|
curr.end.minus(shift))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildIntervals(
|
||||||
|
freq: Frequency,
|
||||||
|
entries: List<Entry>,
|
||||||
|
): ArrayList<Interval> {
|
||||||
|
val filtered = entries.filter { it.value == YES_MANUAL }
|
||||||
|
val num = freq.numerator
|
||||||
|
val den = freq.denominator
|
||||||
|
val intervals = arrayListOf<Interval>()
|
||||||
|
for (i in num - 1 until filtered.size) {
|
||||||
|
val (begin, _) = filtered[i]
|
||||||
|
val (center, _) = filtered[i - num + 1]
|
||||||
|
if (begin.daysUntil(center) < den) {
|
||||||
|
val end = begin.plus(den - 1)
|
||||||
|
intervals.add(Interval(begin, center, end))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return intervals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.MatcherAssert.*
|
||||||
|
import org.hamcrest.core.IsEqual.*
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.NO
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
||||||
|
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.utils.*
|
||||||
|
import org.junit.*
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class EntriesTest {
|
||||||
|
@Test
|
||||||
|
fun testEmptyList() {
|
||||||
|
val entries = Entries()
|
||||||
|
val today = DateUtils.getToday()
|
||||||
|
|
||||||
|
assertEquals(Entry(today.minus(0), UNKNOWN), entries.get(today.minus(0)))
|
||||||
|
assertEquals(Entry(today.minus(2), UNKNOWN), entries.get(today.minus(2)))
|
||||||
|
assertEquals(Entry(today.minus(5), UNKNOWN), entries.get(today.minus(5)))
|
||||||
|
|
||||||
|
entries.add(Entry(today.minus(0), 10))
|
||||||
|
entries.add(Entry(today.minus(0), 15)) // replace previous one
|
||||||
|
entries.add(Entry(today.minus(5), 20))
|
||||||
|
entries.add(Entry(today.minus(8), 30))
|
||||||
|
assertEquals(Entry(today.minus(0), 15), entries.get(today.minus(0)))
|
||||||
|
assertEquals(Entry(today.minus(5), 20), entries.get(today.minus(5)))
|
||||||
|
assertEquals(Entry(today.minus(8), 30), entries.get(today.minus(8)))
|
||||||
|
|
||||||
|
val known = entries.getKnown()
|
||||||
|
assertEquals(3, known.size)
|
||||||
|
assertEquals(Entry(today.minus(0), 15), known[0])
|
||||||
|
assertEquals(Entry(today.minus(5), 20), known[1])
|
||||||
|
assertEquals(Entry(today.minus(8), 30), known[2])
|
||||||
|
|
||||||
|
val actual = entries.getByInterval(today.minus(5), today)
|
||||||
|
assertEquals(6, actual.size)
|
||||||
|
assertEquals(Entry(today.minus(0), 15), actual[0])
|
||||||
|
assertEquals(Entry(today.minus(1), UNKNOWN), actual[1])
|
||||||
|
assertEquals(Entry(today.minus(2), UNKNOWN), actual[2])
|
||||||
|
assertEquals(Entry(today.minus(3), UNKNOWN), actual[3])
|
||||||
|
assertEquals(Entry(today.minus(4), UNKNOWN), actual[4])
|
||||||
|
assertEquals(Entry(today.minus(5), 20), actual[5])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testComputeBoolean() {
|
||||||
|
val today = DateUtils.getToday()
|
||||||
|
|
||||||
|
val original = Entries()
|
||||||
|
original.add(Entry(today.minus(4), YES_MANUAL))
|
||||||
|
original.add(Entry(today.minus(9), YES_MANUAL))
|
||||||
|
original.add(Entry(today.minus(10), YES_MANUAL))
|
||||||
|
|
||||||
|
val computed = Entries()
|
||||||
|
computed.computeFrom(original, Frequency(1, 3), isNumerical = false)
|
||||||
|
|
||||||
|
val expected = listOf(
|
||||||
|
Entry(today.minus(2), YES_AUTO),
|
||||||
|
Entry(today.minus(3), YES_AUTO),
|
||||||
|
Entry(today.minus(4), YES_MANUAL),
|
||||||
|
Entry(today.minus(7), YES_AUTO),
|
||||||
|
Entry(today.minus(8), YES_AUTO),
|
||||||
|
Entry(today.minus(9), YES_MANUAL),
|
||||||
|
Entry(today.minus(10), YES_MANUAL),
|
||||||
|
Entry(today.minus(11), YES_AUTO),
|
||||||
|
Entry(today.minus(12), YES_AUTO),
|
||||||
|
)
|
||||||
|
assertEquals(expected, computed.getKnown())
|
||||||
|
|
||||||
|
// Second call should replace all previously added entries
|
||||||
|
computed.computeFrom(Entries(), Frequency(1, 3), isNumerical = false)
|
||||||
|
assertEquals(listOf(), computed.getKnown())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testComputeNumerical() {
|
||||||
|
val today = DateUtils.getToday()
|
||||||
|
|
||||||
|
val original = Entries()
|
||||||
|
original.add(Entry(today.minus(4), 100))
|
||||||
|
original.add(Entry(today.minus(9), 200))
|
||||||
|
original.add(Entry(today.minus(10), 300))
|
||||||
|
|
||||||
|
val computed = Entries()
|
||||||
|
computed.computeFrom(original, Frequency.DAILY, isNumerical = true)
|
||||||
|
|
||||||
|
val expected = listOf(
|
||||||
|
Entry(today.minus(4), 100),
|
||||||
|
Entry(today.minus(9), 200),
|
||||||
|
Entry(today.minus(10), 300),
|
||||||
|
)
|
||||||
|
assertEquals(expected, computed.getKnown())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGroupBy() {
|
||||||
|
val offsets = intArrayOf(
|
||||||
|
0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
|
||||||
|
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
|
||||||
|
166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295,
|
||||||
|
302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367,
|
||||||
|
372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449,
|
||||||
|
455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507)
|
||||||
|
|
||||||
|
val values = intArrayOf(
|
||||||
|
230, 306, 148, 281, 134, 285, 104, 158, 325, 236, 303, 210, 118, 124,
|
||||||
|
301, 201, 156, 376, 347, 367, 396, 134, 160, 381, 155, 354, 231, 134, 164, 354,
|
||||||
|
236, 398, 199, 221, 208, 397, 253, 276, 214, 341, 299, 221, 353, 250, 341, 168,
|
||||||
|
374, 205, 182, 217, 297, 321, 104, 237, 294, 110, 136, 229, 102, 271, 250, 294,
|
||||||
|
158, 319, 379, 126, 282, 155, 288, 159, 215, 247, 207, 226, 244, 158, 371, 219,
|
||||||
|
272, 228, 350, 153, 356, 279, 394, 202, 213, 214, 112, 248, 139, 245, 165, 256,
|
||||||
|
370, 187, 208, 231, 341, 312)
|
||||||
|
|
||||||
|
val reference = Timestamp.from(2014, Calendar.JUNE, 1)
|
||||||
|
val entries = Entries()
|
||||||
|
offsets.indices.forEach {
|
||||||
|
entries.add(Entry(reference.minus(offsets[it]), values[it]))
|
||||||
|
}
|
||||||
|
|
||||||
|
val byMonth = entries.groupBy(
|
||||||
|
field = DateUtils.TruncateField.MONTH,
|
||||||
|
firstWeekday = Calendar.SATURDAY,
|
||||||
|
isNumerical = true,
|
||||||
|
)
|
||||||
|
assertThat(byMonth.size, equalTo(17))
|
||||||
|
assertThat(byMonth[0], equalTo(Entry(Timestamp.from(2014, Calendar.JUNE, 1), 230)))
|
||||||
|
assertThat(byMonth[6], equalTo(Entry(Timestamp.from(2013, Calendar.DECEMBER, 1), 1988)))
|
||||||
|
assertThat(byMonth[12], equalTo(Entry(Timestamp.from(2013, Calendar.MAY, 1), 1271)))
|
||||||
|
|
||||||
|
val byQuarter = entries.groupBy(
|
||||||
|
field = DateUtils.TruncateField.QUARTER,
|
||||||
|
firstWeekday = Calendar.SATURDAY,
|
||||||
|
isNumerical = true,
|
||||||
|
)
|
||||||
|
assertThat(byQuarter.size, equalTo(6))
|
||||||
|
assertThat(byQuarter[0], equalTo(Entry(Timestamp.from(2014, Calendar.APRIL, 1), 3263)))
|
||||||
|
assertThat(byQuarter[3], equalTo(Entry(Timestamp.from(2013, Calendar.JULY, 1), 3838)))
|
||||||
|
assertThat(byQuarter[5], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 4975)))
|
||||||
|
|
||||||
|
val byYear = entries.groupBy(
|
||||||
|
field = DateUtils.TruncateField.YEAR,
|
||||||
|
firstWeekday = Calendar.SATURDAY,
|
||||||
|
isNumerical = true,
|
||||||
|
)
|
||||||
|
assertThat(byYear.size, equalTo(2))
|
||||||
|
assertThat(byYear[0], equalTo(Entry(Timestamp.from(2014, Calendar.JANUARY, 1), 8227)))
|
||||||
|
assertThat(byYear[1], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 16172)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAddFromInterval() {
|
||||||
|
val entries = listOf(
|
||||||
|
Entry(day(1), YES_MANUAL),
|
||||||
|
Entry(day(2), NO),
|
||||||
|
Entry(day(5), YES_MANUAL),
|
||||||
|
Entry(day(10), YES_MANUAL),
|
||||||
|
)
|
||||||
|
val intervals = listOf(
|
||||||
|
Entries.Interval(day(2), day(2), day(1)),
|
||||||
|
Entries.Interval(day(6), day(5), day(4)),
|
||||||
|
Entries.Interval(day(10), day(8), day(8)),
|
||||||
|
)
|
||||||
|
val expected = listOf(
|
||||||
|
Entry(day(1), YES_MANUAL),
|
||||||
|
Entry(day(2), NO),
|
||||||
|
Entry(day(3), UNKNOWN),
|
||||||
|
Entry(day(4), YES_AUTO),
|
||||||
|
Entry(day(5), YES_MANUAL),
|
||||||
|
Entry(day(6), YES_AUTO),
|
||||||
|
Entry(day(7), UNKNOWN),
|
||||||
|
Entry(day(8), YES_AUTO),
|
||||||
|
Entry(day(9), YES_AUTO),
|
||||||
|
Entry(day(10), YES_MANUAL),
|
||||||
|
)
|
||||||
|
val actual = Entries.buildEntriesFromInterval(entries, intervals)
|
||||||
|
assertThat(actual, equalTo(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSnapIntervalsTogether1() {
|
||||||
|
val original = arrayListOf(
|
||||||
|
Entries.Interval(day(8), day(8), day(2)),
|
||||||
|
Entries.Interval(day(12), day(12), day(6)),
|
||||||
|
Entries.Interval(day(20), day(20), day(14)),
|
||||||
|
Entries.Interval(day(27), day(27), day(21)),
|
||||||
|
)
|
||||||
|
val expected = arrayListOf(
|
||||||
|
Entries.Interval(day(8), day(8), day(2)),
|
||||||
|
Entries.Interval(day(15), day(12), day(9)),
|
||||||
|
Entries.Interval(day(22), day(20), day(16)),
|
||||||
|
Entries.Interval(day(29), day(27), day(23)),
|
||||||
|
)
|
||||||
|
Entries.snapIntervalsTogether(original)
|
||||||
|
assertThat(original, equalTo(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSnapIntervalsTogether2() {
|
||||||
|
val original = arrayListOf(
|
||||||
|
Entries.Interval(day(6), day(4), day(0)),
|
||||||
|
Entries.Interval(day(11), day(8), day(5)),
|
||||||
|
)
|
||||||
|
val expected = arrayListOf(
|
||||||
|
Entries.Interval(day(6), day(4), day(0)),
|
||||||
|
Entries.Interval(day(13), day(8), day(7)),
|
||||||
|
)
|
||||||
|
Entries.snapIntervalsTogether(original)
|
||||||
|
assertThat(original, equalTo(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuildIntervals1() {
|
||||||
|
val entries = listOf(
|
||||||
|
Entry(day(8), YES_MANUAL),
|
||||||
|
Entry(day(18), YES_MANUAL),
|
||||||
|
Entry(day(23), YES_MANUAL),
|
||||||
|
)
|
||||||
|
val expected = listOf(
|
||||||
|
Entries.Interval(day(8), day(8), day(2)),
|
||||||
|
Entries.Interval(day(18), day(18), day(12)),
|
||||||
|
Entries.Interval(day(23), day(23), day(17)),
|
||||||
|
)
|
||||||
|
val actual = Entries.buildIntervals(Frequency.WEEKLY, entries)
|
||||||
|
assertThat(actual, equalTo(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuildIntervals2() {
|
||||||
|
val entries = listOf(
|
||||||
|
Entry(day(8), YES_MANUAL),
|
||||||
|
Entry(day(18), YES_MANUAL),
|
||||||
|
Entry(day(23), YES_MANUAL),
|
||||||
|
)
|
||||||
|
val expected = listOf(
|
||||||
|
Entries.Interval(day(8), day(8), day(8)),
|
||||||
|
Entries.Interval(day(18), day(18), day(18)),
|
||||||
|
Entries.Interval(day(23), day(23), day(23)),
|
||||||
|
)
|
||||||
|
val actual = Entries.buildIntervals(Frequency.DAILY, entries)
|
||||||
|
assertThat(actual, equalTo(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuildIntervals3() {
|
||||||
|
val entries = listOf(
|
||||||
|
Entry(day(8), YES_MANUAL),
|
||||||
|
Entry(day(15), YES_MANUAL),
|
||||||
|
Entry(day(18), YES_MANUAL),
|
||||||
|
Entry(day(22), YES_MANUAL),
|
||||||
|
Entry(day(23), YES_MANUAL),
|
||||||
|
)
|
||||||
|
val expected = listOf(
|
||||||
|
Entries.Interval(day(18), day(15), day(12)),
|
||||||
|
Entries.Interval(day(22), day(18), day(16)),
|
||||||
|
Entries.Interval(day(23), day(22), day(17)),
|
||||||
|
)
|
||||||
|
val actual = Entries.buildIntervals(Frequency.TWO_TIMES_PER_WEEK, entries)
|
||||||
|
assertThat(actual, equalTo(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuildIntervals4() {
|
||||||
|
val entries = listOf(
|
||||||
|
Entry(day(10), YES_MANUAL),
|
||||||
|
Entry(day(20), Entry.SKIP),
|
||||||
|
Entry(day(30), YES_MANUAL),
|
||||||
|
)
|
||||||
|
val expected = listOf(
|
||||||
|
Entries.Interval(day(10), day(10), day(8)),
|
||||||
|
Entries.Interval(day(30), day(30), day(28)),
|
||||||
|
)
|
||||||
|
val actual = Entries.buildIntervals(Frequency(1, 3), entries)
|
||||||
|
assertThat(actual, equalTo(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun day(offset: Int) = DateUtils.getToday().minus(offset)
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in new issue