Implement alternative checkmark algorithm
Fix failing tests
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
find app/build/outputs/failed/test-screenshots -name '*.expected*' -delete
|
||||
rsync -av app/build/outputs/failed/test-screenshots/ app/src/androidTest/assets/
|
||||
find uhabits-android/build/outputs/failed/test-screenshots -name '*.expected*' -delete
|
||||
rsync -av uhabits-android/build/outputs/failed/test-screenshots/ uhabits-android/src/androidTest/assets/
|
||||
|
||||
@@ -5,7 +5,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.0-alpha2'
|
||||
classpath 'com.android.tools.build:gradle:3.0.0-alpha3'
|
||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.4'
|
||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||
|
||||
@@ -96,9 +96,10 @@ dependencies {
|
||||
testAnnotationProcessor 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||
testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.9'
|
||||
testAnnotationProcessor 'com.jakewharton:butterknife-compiler:8.6.1-SNAPSHOT'
|
||||
testCompile 'com.google.dagger:dagger:2.9'
|
||||
testCompile "org.mockito:mockito-core:2.8.9"
|
||||
testCompile "org.mockito:mockito-inline:2.8.9"
|
||||
testImplementation 'com.google.dagger:dagger:2.9'
|
||||
testImplementation "org.mockito:mockito-core:2.8.9"
|
||||
testImplementation "org.mockito:mockito-inline:2.8.9"
|
||||
testImplementation 'junit:junit:4+'
|
||||
|
||||
implementation('com.opencsv:opencsv:3.9') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 22 KiB |
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Á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.models.sqlite;
|
||||
|
||||
import android.support.test.runner.*;
|
||||
import android.test.suitebuilder.annotation.*;
|
||||
|
||||
import com.activeandroid.query.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
import org.isoron.uhabits.models.sqlite.records.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@MediumTest
|
||||
public class SQLiteCheckmarkListTest extends BaseAndroidTest
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
private CheckmarkList checkmarks;
|
||||
|
||||
private long today;
|
||||
|
||||
private long day;
|
||||
|
||||
@Override
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
|
||||
habit = fixtures.createLongHabit();
|
||||
checkmarks = habit.getCheckmarks();
|
||||
checkmarks.getToday(); // compute checkmarks
|
||||
|
||||
today = DateUtils.getStartOfToday();
|
||||
day = DateUtils.millisecondsInOneDay;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd()
|
||||
{
|
||||
checkmarks.invalidateNewerThan(0);
|
||||
|
||||
List<Checkmark> list = new LinkedList<>();
|
||||
list.add(new Checkmark(0, 0));
|
||||
list.add(new Checkmark(1, 1));
|
||||
list.add(new Checkmark(2, 2));
|
||||
|
||||
checkmarks.add(list);
|
||||
|
||||
List<CheckmarkRecord> records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(3));
|
||||
assertThat(records.get(0).timestamp, equalTo(2L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByInterval()
|
||||
{
|
||||
long from = today - 10 * day;
|
||||
long to = today - 3 * day;
|
||||
|
||||
List<Checkmark> list = checkmarks.getByInterval(from, to);
|
||||
assertThat(list.size(), equalTo(8));
|
||||
|
||||
assertThat(list.get(0).getTimestamp(), equalTo(today - 3 * day));
|
||||
assertThat(list.get(3).getTimestamp(), equalTo(today - 6 * day));
|
||||
assertThat(list.get(7).getTimestamp(), equalTo(today - 10 * day));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByInterval_withLongInterval()
|
||||
{
|
||||
long from = today - 200 * day;
|
||||
long to = today;
|
||||
|
||||
List<Checkmark> list = checkmarks.getByInterval(from, to);
|
||||
assertThat(list.size(), equalTo(201));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateNewerThan()
|
||||
{
|
||||
List<CheckmarkRecord> records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(121));
|
||||
|
||||
checkmarks.invalidateNewerThan(today - 20 * day);
|
||||
|
||||
records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(100));
|
||||
assertThat(records.get(0).timestamp, equalTo(today - 21 * day));
|
||||
}
|
||||
|
||||
private List<CheckmarkRecord> getAllRecords()
|
||||
{
|
||||
return new Select()
|
||||
.from(CheckmarkRecord.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("timestamp desc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Á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.models.sqlite;
|
||||
|
||||
import android.support.test.runner.*;
|
||||
import android.test.suitebuilder.annotation.*;
|
||||
|
||||
import com.activeandroid.query.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
import org.isoron.uhabits.models.sqlite.records.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
|
||||
@SuppressWarnings("JavaDoc")
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@MediumTest
|
||||
public class SQLiteScoreListTest extends BaseAndroidTest
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
private ScoreList scores;
|
||||
|
||||
private long today;
|
||||
|
||||
private long day;
|
||||
|
||||
@Override
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
|
||||
habit = fixtures.createLongHabit();
|
||||
scores = habit.getScores();
|
||||
|
||||
today = DateUtils.getStartOfToday();
|
||||
day = DateUtils.millisecondsInOneDay;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAll()
|
||||
{
|
||||
List<Score> list = scores.toList();
|
||||
assertThat(list.size(), equalTo(121));
|
||||
assertThat(list.get(0).getTimestamp(), equalTo(today));
|
||||
assertThat(list.get(10).getTimestamp(), equalTo(today - 10 * day));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateNewerThan()
|
||||
{
|
||||
scores.getTodayValue(); // force recompute
|
||||
List<ScoreRecord> records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(121));
|
||||
|
||||
scores.invalidateNewerThan(today - 10 * day);
|
||||
|
||||
records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(110));
|
||||
assertThat(records.get(0).timestamp, equalTo(today - 11 * day));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd()
|
||||
{
|
||||
new Delete().from(ScoreRecord.class).execute();
|
||||
|
||||
List<Score> list = new LinkedList<>();
|
||||
list.add(new Score(today, 0));
|
||||
list.add(new Score(today - day, 0));
|
||||
list.add(new Score(today - 2 * day, 0));
|
||||
|
||||
scores.add(list);
|
||||
|
||||
List<ScoreRecord> records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(3));
|
||||
assertThat(records.get(0).timestamp, equalTo(today));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByInterval()
|
||||
{
|
||||
long from = today - 10 * day;
|
||||
long to = today - 3 * day;
|
||||
|
||||
List<Score> list = scores.getByInterval(from, to);
|
||||
assertThat(list.size(), equalTo(8));
|
||||
|
||||
assertThat(list.get(0).getTimestamp(), equalTo(today - 3 * day));
|
||||
assertThat(list.get(3).getTimestamp(), equalTo(today - 6 * day));
|
||||
assertThat(list.get(7).getTimestamp(), equalTo(today - 10 * day));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByInterval_withLongInterval()
|
||||
{
|
||||
long from = today - 200 * day;
|
||||
long to = today;
|
||||
|
||||
List<Score> list = scores.getByInterval(from, to);
|
||||
assertThat(list.size(), equalTo(201));
|
||||
}
|
||||
|
||||
private List<ScoreRecord> getAllRecords()
|
||||
{
|
||||
return new Select()
|
||||
.from(ScoreRecord.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("timestamp desc")
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
package org.isoron.uhabits.activities.habits.list;
|
||||
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
|
||||
import org.isoron.androidbase.activities.*;
|
||||
@@ -28,7 +27,6 @@ import org.isoron.uhabits.activities.habits.list.model.*;
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
import org.isoron.uhabits.core.ui.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
import org.isoron.uhabits.sync.*;
|
||||
|
||||
/**
|
||||
* Activity that allows the user to see and modify the list of habits.
|
||||
@@ -85,8 +83,8 @@ public class ListHabitsActivity extends BaseActivity
|
||||
screen.setSelectionMenu(selectionMenu);
|
||||
rootView.setController(controller, selectionMenu);
|
||||
|
||||
if(prefs.isSyncEnabled())
|
||||
startService(new Intent(this, SyncService.class));
|
||||
// if(prefs.isSyncEnabled())
|
||||
// startService(new Intent(this, SyncService.class));
|
||||
|
||||
setScreen(screen);
|
||||
controller.onStartup();
|
||||
|
||||
@@ -188,9 +188,6 @@ public class SQLiteHabitList extends HabitList
|
||||
@Override
|
||||
public void removeAll()
|
||||
{
|
||||
sqlite.query("delete from checkmarks", null);
|
||||
sqlite.query("delete from score", null);
|
||||
sqlite.query("delete from streak", null);
|
||||
sqlite.query("delete from repetitions", null);
|
||||
sqlite.query("delete from habits", null);
|
||||
}
|
||||
|
||||
@@ -6,29 +6,23 @@ apply plugin: 'java'
|
||||
apply plugin: 'jacoco'
|
||||
|
||||
dependencies {
|
||||
|
||||
apt 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||
apt 'com.google.dagger:dagger:2.11-rc2'
|
||||
|
||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||
compileOnly 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||
compileOnly 'com.google.dagger:dagger:2.11-rc2'
|
||||
|
||||
implementation 'com.android.support:support-annotations:25.3.1'
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
implementation 'org.apache.commons:commons-lang3:3.5'
|
||||
implementation 'com.google.code.gson:gson:2.7'
|
||||
|
||||
testImplementation 'junit:junit:4+'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:1.4-atlassian-1'
|
||||
testImplementation 'org.apache.commons:commons-io:1.3.2'
|
||||
testImplementation 'org.mockito:mockito-core:2.8.9'
|
||||
testImplementation 'org.json:json:20160810'
|
||||
|
||||
implementation('com.opencsv:opencsv:3.9') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
}
|
||||
compile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
|
||||
@@ -56,10 +56,10 @@ public final class Checkmark
|
||||
|
||||
/**
|
||||
* The value of the checkmark.
|
||||
*
|
||||
* <p>
|
||||
* For boolean habits, this equals either UNCHECKED, CHECKED_EXPLICITLY,
|
||||
* or CHECKED_IMPLICITLY.
|
||||
*
|
||||
* <p>
|
||||
* For numerical habits, this number is stored in thousandths. That
|
||||
* is, if the user enters value 1.50 on the app, it is stored as 1500.
|
||||
*/
|
||||
@@ -76,6 +76,21 @@ public final class Checkmark
|
||||
return Long.signum(this.getTimestamp() - other.getTimestamp());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Checkmark checkmark = (Checkmark) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(timestamp, checkmark.timestamp)
|
||||
.append(value, checkmark.value)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
public long getTimestamp()
|
||||
{
|
||||
return timestamp;
|
||||
@@ -86,6 +101,15 @@ public final class Checkmark
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(timestamp)
|
||||
.append(value)
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.core.models;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
@@ -29,7 +30,10 @@ import java.util.*;
|
||||
|
||||
import javax.annotation.concurrent.*;
|
||||
|
||||
import static org.isoron.uhabits.core.models.Checkmark.*;
|
||||
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_EXPLICITLY;
|
||||
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_IMPLICITLY;
|
||||
import static org.isoron.uhabits.core.models.Checkmark.UNCHECKED;
|
||||
|
||||
/**
|
||||
* The collection of {@link Checkmark}s belonging to a habit.
|
||||
*/
|
||||
@@ -46,6 +50,104 @@ public abstract class CheckmarkList
|
||||
this.observable = new ModelObservable();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static List<Checkmark> buildCheckmarksFromIntervals(Repetition[] reps,
|
||||
ArrayList<Interval> intervals)
|
||||
{
|
||||
if (reps.length == 0) throw new IllegalArgumentException();
|
||||
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
long today = DateUtils.getStartOfToday();
|
||||
|
||||
long begin = reps[0].getTimestamp();
|
||||
if (intervals.size() > 0)
|
||||
begin = Long.min(begin, intervals.get(0).begin);
|
||||
|
||||
int nDays = (int) ((today - begin) / day) + 1;
|
||||
|
||||
List<Checkmark> checkmarks = new ArrayList<>(nDays);
|
||||
for (int i = 0; i < nDays; i++)
|
||||
checkmarks.add(new Checkmark(today - day * i, UNCHECKED));
|
||||
|
||||
for (Interval interval : intervals)
|
||||
{
|
||||
for (long date = interval.begin; date <= interval.end; date += day)
|
||||
{
|
||||
if (date > today) continue;
|
||||
int offset = (int) ((today - date) / day);
|
||||
checkmarks.set(offset, new Checkmark(date, CHECKED_IMPLICITLY));
|
||||
}
|
||||
}
|
||||
|
||||
for (Repetition rep : reps)
|
||||
{
|
||||
long date = rep.getTimestamp();
|
||||
int offset = (int) ((today - date) / day);
|
||||
checkmarks.set(offset, new Checkmark(date, CHECKED_EXPLICITLY));
|
||||
}
|
||||
|
||||
return checkmarks;
|
||||
}
|
||||
|
||||
/**
|
||||
* For non-daily habits, some groups of repetitions generate many
|
||||
* checkmarks. For example, for weekly habits, each repetition generates
|
||||
* seven checkmarks. For twice-a-week habits, two repetitions that are close
|
||||
* enough together also generate seven checkmarks.
|
||||
* <p>
|
||||
* This group of generated checkmarks, for a given set of repetition, is
|
||||
* represented by an interval. This function computes the list of intervals
|
||||
* for a given list of repetitions. It tries to build the intervals as far
|
||||
* away in the future as possible.
|
||||
*/
|
||||
@NonNull
|
||||
static ArrayList<Interval> buildIntervals(@NonNull Frequency freq,
|
||||
@NonNull Repetition[] reps)
|
||||
{
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
int num = freq.getNumerator();
|
||||
int den = freq.getDenominator();
|
||||
|
||||
ArrayList<Interval> intervals = new ArrayList<>();
|
||||
for (int i = 0; i < reps.length - num + 1; i++)
|
||||
{
|
||||
Repetition first = reps[i];
|
||||
Repetition last = reps[i + num - 1];
|
||||
|
||||
long distance = (last.getTimestamp() - first.getTimestamp()) / day;
|
||||
if (distance >= den) continue;
|
||||
|
||||
long begin = first.getTimestamp();
|
||||
long center = last.getTimestamp();
|
||||
long end = begin + (den - 1) * day;
|
||||
intervals.add(new Interval(begin, center, end));
|
||||
}
|
||||
|
||||
return intervals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting from the oldest interval, this function tries to slide the
|
||||
* intervals backwards into the past, so that gaps are eliminated and
|
||||
* streaks are maximized. When it detects that sliding an interval
|
||||
* would not help fixing any gap, it leaves the interval unchanged.
|
||||
*/
|
||||
static void snapIntervalsTogether(@NonNull ArrayList<Interval> intervals)
|
||||
{
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
|
||||
for (int i = 1; i < intervals.size(); i++)
|
||||
{
|
||||
Interval curr = intervals.get(i);
|
||||
Interval prev = intervals.get(i - 1);
|
||||
|
||||
long distance = curr.begin - prev.end - day;
|
||||
if (distance <= 0 || curr.end - distance < curr.center) continue;
|
||||
intervals.set(i, new Interval(curr.begin - distance, curr.center,
|
||||
curr.end - distance));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the given checkmarks to the list.
|
||||
* <p>
|
||||
@@ -104,8 +206,9 @@ public abstract class CheckmarkList
|
||||
@Nullable
|
||||
public synchronized final Checkmark getToday()
|
||||
{
|
||||
computeAll();
|
||||
return getNewestComputed();
|
||||
compute();
|
||||
long today = DateUtils.getStartOfToday();
|
||||
return getByInterval(today, today).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +220,7 @@ public abstract class CheckmarkList
|
||||
{
|
||||
Checkmark today = getToday();
|
||||
if (today != null) return today.getValue();
|
||||
else return Checkmark.UNCHECKED;
|
||||
else return UNCHECKED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +271,7 @@ public abstract class CheckmarkList
|
||||
|
||||
synchronized (this)
|
||||
{
|
||||
computeAll();
|
||||
compute();
|
||||
values = getAllValues();
|
||||
}
|
||||
|
||||
@@ -184,111 +287,28 @@ public abstract class CheckmarkList
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and stores one checkmark for each day that falls inside the
|
||||
* specified interval of time. Days that already have a corresponding
|
||||
* checkmark are skipped.
|
||||
*
|
||||
* This method assumes the list of computed checkmarks has no holes. That
|
||||
* is, if there is a checkmark computed at time t1 and another at time t2,
|
||||
* then every checkmark between t1 and t2 is also computed.
|
||||
*
|
||||
* @param from timestamp for the beginning of the interval
|
||||
* @param to timestamp for the end of the interval
|
||||
* Computes and stores one checkmark for each day, from the first habit
|
||||
* repetition to today. If this list is already computed, does nothing.
|
||||
*/
|
||||
protected final synchronized void compute(long from, long to)
|
||||
protected final synchronized void compute()
|
||||
{
|
||||
final long day = DateUtils.millisecondsInOneDay;
|
||||
final long today = DateUtils.getStartOfToday();
|
||||
|
||||
Checkmark newest = getNewestComputed();
|
||||
Checkmark oldest = getOldestComputed();
|
||||
if(newest != null && newest.getTimestamp() == today) return;
|
||||
invalidateNewerThan(0);
|
||||
|
||||
if (newest == null || oldest == null)
|
||||
{
|
||||
forceRecompute(from, to);
|
||||
}
|
||||
else
|
||||
{
|
||||
forceRecompute(from, oldest.getTimestamp() - day);
|
||||
forceRecompute(newest.getTimestamp() + day, to);
|
||||
}
|
||||
}
|
||||
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||
if (oldestRep == null) return;
|
||||
final long from = oldestRep.getTimestamp();
|
||||
|
||||
/**
|
||||
* Returns oldest checkmark that has already been computed.
|
||||
*
|
||||
* @return oldest checkmark already computed
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Checkmark getOldestComputed();
|
||||
Repetition reps[] = habit
|
||||
.getRepetitions()
|
||||
.getByInterval(from, today)
|
||||
.toArray(new Repetition[0]);
|
||||
|
||||
/**
|
||||
* Computes and stores one checkmark for each day that falls inside the
|
||||
* specified interval of time.
|
||||
*
|
||||
* This method does not check if the checkmarks have already been
|
||||
* computed or not. If they have, then duplicate checkmarks will
|
||||
* be stored, which is a bad thing.
|
||||
*
|
||||
* @param from timestamp for the beginning of the interval
|
||||
* @param to timestamp for the end of the interval
|
||||
*/
|
||||
private synchronized void forceRecompute(long from, long to)
|
||||
{
|
||||
if (from > to) return;
|
||||
|
||||
final long day = DateUtils.millisecondsInOneDay;
|
||||
Frequency freq = habit.getFrequency();
|
||||
|
||||
long fromExtended = from - (long) (freq.getDenominator()) * day;
|
||||
List<Repetition> reps =
|
||||
habit.getRepetitions().getByInterval(fromExtended, to);
|
||||
|
||||
final int nDays = (int) ((to - from) / day) + 1;
|
||||
int nDaysExtended = (int) ((to - fromExtended) / day) + 1;
|
||||
final int checks[] = new int[nDaysExtended];
|
||||
|
||||
for (Repetition rep : reps)
|
||||
{
|
||||
int offset = (int) ((rep.getTimestamp() - fromExtended) / day);
|
||||
checks[nDaysExtended - offset - 1] = rep.getValue();
|
||||
}
|
||||
|
||||
for (int i = 0; i < nDays; i++)
|
||||
{
|
||||
int counter = 0;
|
||||
|
||||
for (int j = 0; j < freq.getDenominator(); j++)
|
||||
if (checks[i + j] == CHECKED_EXPLICITLY) counter++;
|
||||
|
||||
if (counter >= freq.getNumerator())
|
||||
if (checks[i] != CHECKED_EXPLICITLY)
|
||||
checks[i] = CHECKED_IMPLICITLY;
|
||||
}
|
||||
|
||||
List<Checkmark> checkmarks = new LinkedList<>();
|
||||
|
||||
for (int i = 0; i < nDays; i++)
|
||||
{
|
||||
int value = checks[i];
|
||||
long timestamp = to - i * day;
|
||||
checkmarks.add(new Checkmark(timestamp, value));
|
||||
}
|
||||
|
||||
add(checkmarks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and stores one checkmark for each day, since the first
|
||||
* repetition of the habit until today. Days that already have a
|
||||
* corresponding checkmark are skipped.
|
||||
*/
|
||||
private synchronized void computeAll()
|
||||
{
|
||||
Repetition oldest = habit.getRepetitions().getOldest();
|
||||
if (oldest == null) return;
|
||||
|
||||
Long today = DateUtils.getStartOfToday();
|
||||
compute(oldest.getTimestamp(), today);
|
||||
if (habit.isNumerical()) computeNumerical(reps);
|
||||
else computeYesNo(reps);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,4 +318,95 @@ public abstract class CheckmarkList
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Checkmark getNewestComputed();
|
||||
|
||||
/**
|
||||
* Returns oldest checkmark that has already been computed.
|
||||
*
|
||||
* @return oldest checkmark already computed
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Checkmark getOldestComputed();
|
||||
|
||||
private void computeNumerical(Repetition[] reps)
|
||||
{
|
||||
if (reps.length == 0) throw new IllegalArgumentException();
|
||||
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
long today = DateUtils.getStartOfToday();
|
||||
long begin = reps[0].getTimestamp();
|
||||
|
||||
int nDays = (int) ((today - begin) / day) + 1;
|
||||
List<Checkmark> checkmarks = new ArrayList<>(nDays);
|
||||
for (int i = 0; i < nDays; i++)
|
||||
checkmarks.add(new Checkmark(today - day * i, 0));
|
||||
|
||||
for (Repetition rep : reps)
|
||||
{
|
||||
long date = rep.getTimestamp();
|
||||
int offset = (int) ((today - date) / day);
|
||||
checkmarks.set(offset, new Checkmark(date, rep.getValue()));
|
||||
}
|
||||
|
||||
add(checkmarks);
|
||||
}
|
||||
|
||||
private void computeYesNo(Repetition[] reps)
|
||||
{
|
||||
ArrayList<Interval> intervals;
|
||||
intervals = buildIntervals(habit.getFrequency(), reps);
|
||||
snapIntervalsTogether(intervals);
|
||||
add(buildCheckmarksFromIntervals(reps, intervals));
|
||||
}
|
||||
|
||||
static class Interval
|
||||
{
|
||||
final long begin;
|
||||
|
||||
final long center;
|
||||
|
||||
final long end;
|
||||
|
||||
Interval(long begin, long center, long end)
|
||||
{
|
||||
this.begin = begin;
|
||||
this.center = center;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Interval interval = (Interval) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(begin, interval.begin)
|
||||
.append(center, interval.center)
|
||||
.append(end, interval.end)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(begin)
|
||||
.append(center)
|
||||
.append(end)
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this)
|
||||
.append("begin", begin)
|
||||
.append("center", center)
|
||||
.append("end", end)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,9 @@ public final class Repetition
|
||||
/**
|
||||
* The value of the repetition.
|
||||
*
|
||||
* For boolean habits, this equals either Checkmark.UNCHECKED,
|
||||
* Checkmark.CHECKED_EXPLICITLY, or Checkmark.CHECKED_IMPLICITLY.
|
||||
*
|
||||
* For boolean habits, this always equals Checkmark.CHECKED_EXPLICITLY.
|
||||
* For numerical habits, this number is stored in thousandths. That
|
||||
* is, if the user enters value 1.50 on the app, it is stored as 1500.
|
||||
* is, if the user enters value 1.50 on the app, it is here stored as 1500.
|
||||
*/
|
||||
private final int value;
|
||||
|
||||
|
||||
@@ -77,7 +77,6 @@ public abstract class RepetitionList
|
||||
* @param toTimestamp timestamp of the end of the interval
|
||||
* @return list of repetitions within given time interval
|
||||
*/
|
||||
// TODO: Change order timestamp desc
|
||||
public abstract List<Repetition> getByInterval(long fromTimestamp,
|
||||
long toTimestamp);
|
||||
|
||||
|
||||
@@ -25,17 +25,19 @@ import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.isoron.uhabits.core.utils.DateUtils.*;
|
||||
|
||||
/**
|
||||
* In-memory implementation of {@link CheckmarkList}.
|
||||
*/
|
||||
public class MemoryCheckmarkList extends CheckmarkList
|
||||
{
|
||||
LinkedList<Checkmark> list;
|
||||
ArrayList<Checkmark> list;
|
||||
|
||||
public MemoryCheckmarkList(Habit habit)
|
||||
{
|
||||
super(habit);
|
||||
list = new LinkedList<>();
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,13 +51,27 @@ public class MemoryCheckmarkList extends CheckmarkList
|
||||
@Override
|
||||
public List<Checkmark> getByInterval(long fromTimestamp, long toTimestamp)
|
||||
{
|
||||
compute(fromTimestamp, toTimestamp);
|
||||
compute();
|
||||
|
||||
long newestTimestamp = Long.MIN_VALUE;
|
||||
long oldestTimestamp = Long.MAX_VALUE;
|
||||
|
||||
Checkmark newest = getNewestComputed();
|
||||
Checkmark oldest = getOldestComputed();
|
||||
if(newest != null) newestTimestamp = newest.getTimestamp();
|
||||
if(oldest != null) oldestTimestamp = oldest.getTimestamp();
|
||||
|
||||
List<Checkmark> filtered = new LinkedList<>();
|
||||
|
||||
for (Checkmark c : list)
|
||||
if (c.getTimestamp() >= fromTimestamp &&
|
||||
c.getTimestamp() <= toTimestamp) filtered.add(c);
|
||||
for(long time = toTimestamp; time >= fromTimestamp; time -= millisecondsInOneDay)
|
||||
{
|
||||
if(time > newestTimestamp || time < oldestTimestamp)
|
||||
filtered.add(new Checkmark(time, Checkmark.UNCHECKED));
|
||||
else
|
||||
{
|
||||
int offset = (int) ((newestTimestamp - time) / millisecondsInOneDay);
|
||||
filtered.add(list.get(offset));
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
@@ -63,12 +79,8 @@ public class MemoryCheckmarkList extends CheckmarkList
|
||||
@Override
|
||||
public void invalidateNewerThan(long timestamp)
|
||||
{
|
||||
LinkedList<Checkmark> invalid = new LinkedList<>();
|
||||
|
||||
for (Checkmark c : list)
|
||||
if (c.getTimestamp() >= timestamp) invalid.add(c);
|
||||
|
||||
list.removeAll(invalid);
|
||||
list.clear();
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,7 +88,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
||||
protected Checkmark getOldestComputed()
|
||||
{
|
||||
if(list.isEmpty()) return null;
|
||||
return list.getLast();
|
||||
return list.get(list.size()-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,7 +96,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
||||
protected Checkmark getNewestComputed()
|
||||
{
|
||||
if(list.isEmpty()) return null;
|
||||
return list.getFirst();
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,33 +24,153 @@ import org.isoron.uhabits.core.utils.*;
|
||||
import org.junit.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.core.IsEqual.*;
|
||||
import static org.isoron.uhabits.core.models.Checkmark.*;
|
||||
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_EXPLICITLY;
|
||||
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_IMPLICITLY;
|
||||
import static org.isoron.uhabits.core.models.Checkmark.UNCHECKED;
|
||||
|
||||
public class CheckmarkListTest extends BaseUnitTest
|
||||
{
|
||||
// 8:00am, January 25th, 2015 (UTC)
|
||||
private long fixed_local_time = 1422172800000L;
|
||||
private long dayLength;
|
||||
|
||||
private long today;
|
||||
|
||||
private Habit nonDailyHabit;
|
||||
|
||||
private Habit emptyHabit;
|
||||
|
||||
private Habit numericalHabit;
|
||||
|
||||
@Override
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
|
||||
DateUtils.setFixedLocalTime(fixed_local_time);
|
||||
dayLength = DateUtils.millisecondsInOneDay;
|
||||
today = DateUtils.getStartOfToday();
|
||||
|
||||
fixtures.createShortHabit();
|
||||
nonDailyHabit = fixtures.createShortHabit();
|
||||
habitList.add(nonDailyHabit);
|
||||
|
||||
emptyHabit = fixtures.createEmptyHabit();
|
||||
habitList.add(emptyHabit);
|
||||
|
||||
numericalHabit = fixtures.createNumericalHabit();
|
||||
habitList.add(numericalHabit);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_buildCheckmarksFromIntervals_1() throws Exception
|
||||
{
|
||||
Repetition reps[] = new Repetition[]{
|
||||
new Repetition(day(10), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(5), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(2), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(1), CHECKED_EXPLICITLY),
|
||||
};
|
||||
|
||||
ArrayList<CheckmarkList.Interval> intervals = new ArrayList<>();
|
||||
intervals.add(new CheckmarkList.Interval(day(10), day(8), day(8)));
|
||||
intervals.add(new CheckmarkList.Interval(day(6), day(5), day(4)));
|
||||
intervals.add(new CheckmarkList.Interval(day(2), day(2), day(1)));
|
||||
|
||||
List<Checkmark> expected = new ArrayList<>();
|
||||
expected.add(new Checkmark(day(0), UNCHECKED));
|
||||
expected.add(new Checkmark(day(1), CHECKED_EXPLICITLY));
|
||||
expected.add(new Checkmark(day(2), CHECKED_EXPLICITLY));
|
||||
expected.add(new Checkmark(day(3), UNCHECKED));
|
||||
expected.add(new Checkmark(day(4), CHECKED_IMPLICITLY));
|
||||
expected.add(new Checkmark(day(5), CHECKED_EXPLICITLY));
|
||||
expected.add(new Checkmark(day(6), CHECKED_IMPLICITLY));
|
||||
expected.add(new Checkmark(day(7), UNCHECKED));
|
||||
expected.add(new Checkmark(day(8), CHECKED_IMPLICITLY));
|
||||
expected.add(new Checkmark(day(9), CHECKED_IMPLICITLY));
|
||||
expected.add(new Checkmark(day(10), CHECKED_EXPLICITLY));
|
||||
|
||||
List<Checkmark> actual =
|
||||
CheckmarkList.buildCheckmarksFromIntervals(reps, intervals);
|
||||
assertThat(actual, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_buildCheckmarksFromIntervals_2() throws Exception
|
||||
{
|
||||
Repetition reps[] = new Repetition[]{
|
||||
new Repetition(day(0), CHECKED_EXPLICITLY),
|
||||
};
|
||||
|
||||
ArrayList<CheckmarkList.Interval> intervals = new ArrayList<>();
|
||||
intervals.add(new CheckmarkList.Interval(day(0), day(0), day(-10)));
|
||||
|
||||
List<Checkmark> expected = new ArrayList<>();
|
||||
expected.add(new Checkmark(day(0), CHECKED_EXPLICITLY));
|
||||
|
||||
List<Checkmark> actual =
|
||||
CheckmarkList.buildCheckmarksFromIntervals(reps, intervals);
|
||||
assertThat(actual, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_buildIntervals_1() throws Exception
|
||||
{
|
||||
Repetition reps[] = new Repetition[]{
|
||||
new Repetition(day(23), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(18), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(8), CHECKED_EXPLICITLY),
|
||||
};
|
||||
|
||||
ArrayList<CheckmarkList.Interval> expected = new ArrayList<>();
|
||||
expected.add(new CheckmarkList.Interval(day(23), day(23), day(17)));
|
||||
expected.add(new CheckmarkList.Interval(day(18), day(18), day(12)));
|
||||
expected.add(new CheckmarkList.Interval(day(8), day(8), day(2)));
|
||||
|
||||
ArrayList<CheckmarkList.Interval> actual;
|
||||
actual = CheckmarkList.buildIntervals(Frequency.WEEKLY, reps);
|
||||
assertThat(actual, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_buildIntervals_2() throws Exception
|
||||
{
|
||||
Repetition reps[] = new Repetition[]{
|
||||
new Repetition(day(23), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(18), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(8), CHECKED_EXPLICITLY),
|
||||
};
|
||||
|
||||
ArrayList<CheckmarkList.Interval> expected = new ArrayList<>();
|
||||
expected.add(new CheckmarkList.Interval(day(23), day(23), day(23)));
|
||||
expected.add(new CheckmarkList.Interval(day(18), day(18), day(18)));
|
||||
expected.add(new CheckmarkList.Interval(day(8), day(8), day(8)));
|
||||
|
||||
ArrayList<CheckmarkList.Interval> actual;
|
||||
actual = CheckmarkList.buildIntervals(Frequency.DAILY, reps);
|
||||
assertThat(actual, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_buildIntervals_3() throws Exception
|
||||
{
|
||||
Repetition reps[] = new Repetition[]{
|
||||
new Repetition(day(23), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(22), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(18), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(15), CHECKED_EXPLICITLY),
|
||||
new Repetition(day(8), CHECKED_EXPLICITLY),
|
||||
};
|
||||
|
||||
ArrayList<CheckmarkList.Interval> expected = new ArrayList<>();
|
||||
expected.add(new CheckmarkList.Interval(day(23), day(22), day(17)));
|
||||
expected.add(new CheckmarkList.Interval(day(22), day(18), day(16)));
|
||||
expected.add(new CheckmarkList.Interval(day(18), day(15), day(12)));
|
||||
|
||||
ArrayList<CheckmarkList.Interval> actual;
|
||||
actual =
|
||||
CheckmarkList.buildIntervals(Frequency.TWO_TIMES_PER_WEEK, reps);
|
||||
assertThat(actual, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -62,7 +182,7 @@ public class CheckmarkListTest extends BaseUnitTest
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
UNCHECKED,
|
||||
CHECKED_IMPLICITLY,
|
||||
CHECKED_IMPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY
|
||||
@@ -73,7 +193,6 @@ public class CheckmarkListTest extends BaseUnitTest
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test_getAllValues_moveForwardInTime()
|
||||
{
|
||||
@@ -89,7 +208,7 @@ public class CheckmarkListTest extends BaseUnitTest
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
UNCHECKED,
|
||||
CHECKED_IMPLICITLY,
|
||||
CHECKED_IMPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY
|
||||
@@ -119,7 +238,7 @@ public class CheckmarkListTest extends BaseUnitTest
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
UNCHECKED,
|
||||
CHECKED_IMPLICITLY,
|
||||
CHECKED_IMPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY
|
||||
@@ -130,20 +249,33 @@ public class CheckmarkListTest extends BaseUnitTest
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getByInterval_withNumericalHabits() throws Exception
|
||||
{
|
||||
CheckmarkList checkmarks = numericalHabit.getCheckmarks();
|
||||
|
||||
List<Checkmark> expected =
|
||||
Arrays.asList(new Checkmark(day(1), 200), new Checkmark(day(2), 0),
|
||||
new Checkmark(day(3), 300), new Checkmark(day(4), 0),
|
||||
new Checkmark(day(5), 400));
|
||||
|
||||
List<Checkmark> actual = checkmarks.getByInterval(day(5), day(1));
|
||||
assertThat(actual, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getTodayValue()
|
||||
{
|
||||
CheckmarkList checkmarks = nonDailyHabit.getCheckmarks();
|
||||
|
||||
travelInTime(-1);
|
||||
assertThat(nonDailyHabit.getCheckmarks().getTodayValue(),
|
||||
equalTo(UNCHECKED));
|
||||
assertThat(checkmarks.getTodayValue(), equalTo(UNCHECKED));
|
||||
|
||||
travelInTime(0);
|
||||
assertThat(nonDailyHabit.getCheckmarks().getTodayValue(),
|
||||
equalTo(CHECKED_EXPLICITLY));
|
||||
assertThat(checkmarks.getTodayValue(), equalTo(CHECKED_EXPLICITLY));
|
||||
|
||||
travelInTime(1);
|
||||
assertThat(nonDailyHabit.getCheckmarks().getTodayValue(),
|
||||
equalTo(UNCHECKED));
|
||||
assertThat(checkmarks.getTodayValue(), equalTo(UNCHECKED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -156,14 +288,12 @@ public class CheckmarkListTest extends BaseUnitTest
|
||||
@Test
|
||||
public void test_getValues_withValidInterval()
|
||||
{
|
||||
long from =
|
||||
DateUtils.getStartOfToday() - 15 * DateUtils.millisecondsInOneDay;
|
||||
long to =
|
||||
DateUtils.getStartOfToday() - 5 * DateUtils.millisecondsInOneDay;
|
||||
long from = today - 15 * dayLength;
|
||||
long to = today - 5 * dayLength;
|
||||
|
||||
int[] expectedValues = {
|
||||
CHECKED_EXPLICITLY,
|
||||
UNCHECKED,
|
||||
CHECKED_IMPLICITLY,
|
||||
CHECKED_IMPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
CHECKED_EXPLICITLY,
|
||||
@@ -180,18 +310,31 @@ public class CheckmarkListTest extends BaseUnitTest
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_snapIntervalsTogether_1() throws Exception
|
||||
{
|
||||
ArrayList<CheckmarkList.Interval> original = new ArrayList<>();
|
||||
original.add(new CheckmarkList.Interval(day(40), day(40), day(34)));
|
||||
original.add(new CheckmarkList.Interval(day(25), day(25), day(19)));
|
||||
original.add(new CheckmarkList.Interval(day(16), day(16), day(10)));
|
||||
original.add(new CheckmarkList.Interval(day(8), day(8), day(2)));
|
||||
|
||||
ArrayList<CheckmarkList.Interval> expected = new ArrayList<>();
|
||||
expected.add(new CheckmarkList.Interval(day(40), day(40), day(34)));
|
||||
expected.add(new CheckmarkList.Interval(day(25), day(25), day(19)));
|
||||
expected.add(new CheckmarkList.Interval(day(18), day(16), day(12)));
|
||||
expected.add(new CheckmarkList.Interval(day(11), day(8), day(5)));
|
||||
|
||||
CheckmarkList.snapIntervalsTogether(original);
|
||||
assertThat(original, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_writeCSV() throws IOException
|
||||
{
|
||||
String expectedCSV = "2015-01-25,2\n" +
|
||||
"2015-01-24,0\n" +
|
||||
"2015-01-23,1\n" +
|
||||
"2015-01-22,2\n" +
|
||||
"2015-01-21,2\n" +
|
||||
"2015-01-20,2\n" +
|
||||
"2015-01-19,0\n" +
|
||||
"2015-01-18,1\n" +
|
||||
"2015-01-17,2\n" +
|
||||
String expectedCSV = "2015-01-25,2\n2015-01-24,0\n2015-01-23,1\n" +
|
||||
"2015-01-22,2\n2015-01-21,2\n2015-01-20,2\n" +
|
||||
"2015-01-19,1\n2015-01-18,1\n2015-01-17,2\n" +
|
||||
"2015-01-16,2\n";
|
||||
|
||||
|
||||
@@ -201,9 +344,15 @@ public class CheckmarkListTest extends BaseUnitTest
|
||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
||||
}
|
||||
|
||||
private long day(int offset)
|
||||
{
|
||||
return DateUtils.getStartOfToday() -
|
||||
offset * DateUtils.millisecondsInOneDay;
|
||||
}
|
||||
|
||||
private void travelInTime(int days)
|
||||
{
|
||||
DateUtils.setFixedLocalTime(
|
||||
fixed_local_time + days * DateUtils.millisecondsInOneDay);
|
||||
FIXED_LOCAL_TIME + days * DateUtils.millisecondsInOneDay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,28 @@ public class ScoreListTest extends BaseUnitTest
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getValues()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
long today = DateUtils.getStartOfToday();
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
|
||||
long from = today - 4 * day;
|
||||
long to = today - 2 * day;
|
||||
|
||||
double[] expected = {
|
||||
0.617008, 0.596033, 0.573909,
|
||||
};
|
||||
|
||||
double[] actual = habit.getScores().getValues(from, to);
|
||||
assertThat(actual.length, equalTo(expected.length));
|
||||
|
||||
for (int i = 0; i < actual.length; i++)
|
||||
assertThat(actual[i], closeTo(expected[i], E));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_groupBy()
|
||||
{
|
||||
@@ -133,9 +155,9 @@ public class ScoreListTest extends BaseUnitTest
|
||||
habit.getScores().groupBy(DateUtils.TruncateField.MONTH);
|
||||
|
||||
assertThat(list.size(), equalTo(5));
|
||||
assertThat(list.get(0).getValue(), closeTo(0.549096, E));
|
||||
assertThat(list.get(1).getValue(), closeTo(0.480098, E));
|
||||
assertThat(list.get(2).getValue(), closeTo(0.377885, E));
|
||||
assertThat(list.get(0).getValue(), closeTo(0.653659, E));
|
||||
assertThat(list.get(1).getValue(), closeTo(0.622715, E));
|
||||
assertThat(list.get(2).getValue(), closeTo(0.520997, E));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -157,16 +179,11 @@ public class ScoreListTest extends BaseUnitTest
|
||||
{
|
||||
Habit habit = fixtures.createShortHabit();
|
||||
|
||||
String expectedCSV = "2015-01-25,0.2372\n" +
|
||||
"2015-01-24,0.2096\n" +
|
||||
"2015-01-23,0.2172\n" +
|
||||
"2015-01-22,0.1889\n" +
|
||||
"2015-01-21,0.1595\n" +
|
||||
"2015-01-20,0.1291\n" +
|
||||
"2015-01-19,0.0976\n" +
|
||||
"2015-01-18,0.1011\n" +
|
||||
"2015-01-17,0.0686\n" +
|
||||
"2015-01-16,0.0349\n";
|
||||
String expectedCSV = "2015-01-25,0.2654\n2015-01-24,0.2389\n" +
|
||||
"2015-01-23,0.2475\n2015-01-22,0.2203\n" +
|
||||
"2015-01-21,0.1921\n2015-01-20,0.1628\n" +
|
||||
"2015-01-19,0.1325\n2015-01-18,0.1011\n" +
|
||||
"2015-01-17,0.0686\n2015-01-16,0.0349\n";
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
habit.getScores().writeCSV(writer);
|
||||
@@ -174,30 +191,6 @@ public class ScoreListTest extends BaseUnitTest
|
||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getValues()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
long today = DateUtils.getStartOfToday();
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
|
||||
long from = today - 4 * day;
|
||||
long to = today - 2 * day;
|
||||
|
||||
double[] expected = {
|
||||
0.617008,
|
||||
0.596033,
|
||||
0.573909,
|
||||
};
|
||||
|
||||
double[] actual = habit.getScores().getValues(from, to);
|
||||
assertThat(actual.length, equalTo(expected.length));
|
||||
|
||||
for(int i = 0; i < actual.length; i++)
|
||||
assertThat(actual[i], closeTo(expected[i], E));
|
||||
}
|
||||
|
||||
private void toggleRepetitions(final int from, final int to)
|
||||
{
|
||||
RepetitionList reps = habit.getRepetitions();
|
||||
|
||||
@@ -99,6 +99,7 @@ public class ListHabitsBehaviorTest extends BaseUnitTest
|
||||
when(system.getCSVOutputDir()).thenReturn(outputDir);
|
||||
behavior.onExportCSV();
|
||||
verify(screen).showMessage(COULD_NOT_EXPORT);
|
||||
assertTrue(outputDir.delete());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||