diff --git a/accept_images.sh b/accept_images.sh index 4a572232c..e8e14d51c 100755 --- a/accept_images.sh +++ b/accept_images.sh @@ -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/ diff --git a/build.gradle b/build.gradle index 9cc9777e4..09f82f33e 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/uhabits-android/build.gradle b/uhabits-android/build.gradle index a23f7e8f9..83caae219 100644 --- a/uhabits-android/build.gradle +++ b/uhabits-android/build.gradle @@ -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' diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/render.png b/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/render.png index fabd5891e..4b79eb36d 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/render.png and b/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderDataOffset.png b/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderDataOffset.png index 0fee4e308..05f3b7341 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderDataOffset.png and b/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderDataOffset.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderDifferentSize.png b/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderDifferentSize.png index e2a08b0d7..8bd6b25b4 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderDifferentSize.png and b/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderDifferentSize.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderTransparent.png b/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderTransparent.png index b7be74220..4ec75da79 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderTransparent.png and b/uhabits-android/src/androidTest/assets/views-v21/common/HistoryChart/renderTransparent.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/render.png b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/render.png index 00004c4b3..a14dec71b 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/render.png and b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderDataOffset.png b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderDataOffset.png index cda361921..e266b88a0 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderDataOffset.png and b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderDataOffset.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderDifferentSize.png b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderDifferentSize.png index 1156ebc2b..3fa863353 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderDifferentSize.png and b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderDifferentSize.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderMonthly.png b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderMonthly.png index eff5e8c4e..aab4da0c7 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderMonthly.png and b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderMonthly.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderTransparent.png b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderTransparent.png index baa18f736..12c7f8e3d 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderTransparent.png and b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderTransparent.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/render.png b/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/render.png index 4bab05b52..5c6e059d2 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/render.png and b/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/renderSmallSize.png b/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/renderSmallSize.png index 00eb8daa6..6fb3e3763 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/renderSmallSize.png and b/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/renderSmallSize.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/renderTransparent.png b/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/renderTransparent.png index 4bab05b52..5c6e059d2 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/renderTransparent.png and b/uhabits-android/src/androidTest/assets/views-v21/common/StreakChart/renderTransparent.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/show/HistoryCard/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/show/HistoryCard/render.png index 938c30236..eccf19d41 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/show/HistoryCard/render.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/show/HistoryCard/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/show/OverviewCard/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/show/OverviewCard/render.png index e06037b20..31c1098a0 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/show/OverviewCard/render.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/show/OverviewCard/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/show/ScoreCard/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/show/ScoreCard/render.png index 10d008b94..eabe0c6c7 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/show/ScoreCard/render.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/show/ScoreCard/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/show/StreakCard/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/show/StreakCard/render.png index eca454e71..79f5d3051 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/show/StreakCard/render.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/show/StreakCard/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/widgets/StreakWidget/render.png b/uhabits-android/src/androidTest/assets/views-v21/widgets/StreakWidget/render.png index ce2eb44ae..19a2700ca 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/widgets/StreakWidget/render.png and b/uhabits-android/src/androidTest/assets/views-v21/widgets/StreakWidget/render.png differ diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java deleted file mode 100644 index 1efac3177..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2016 Álinson Santos Xavier - * - * 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 . - */ - -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 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 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 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 list = checkmarks.getByInterval(from, to); - assertThat(list.size(), equalTo(201)); - } - - @Test - public void testInvalidateNewerThan() - { - List 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 getAllRecords() - { - return new Select() - .from(CheckmarkRecord.class) - .where("habit = ?", habit.getId()) - .orderBy("timestamp desc") - .execute(); - } - -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java deleted file mode 100644 index 3108ab96a..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2016 Álinson Santos Xavier - * - * 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 . - */ - -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 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 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 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 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 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 list = scores.getByInterval(from, to); - assertThat(list.size(), equalTo(201)); - } - - private List getAllRecords() - { - return new Select() - .from(ScoreRecord.class) - .where("habit = ?", habit.getId()) - .orderBy("timestamp desc") - .execute(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java index ff1feba11..93dba195e 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java @@ -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(); diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java index 6f9106cbb..cfceec388 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java @@ -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); } diff --git a/uhabits-core/build.gradle b/uhabits-core/build.gradle index 47f7cc009..cb77f2da7 100644 --- a/uhabits-core/build.gradle +++ b/uhabits-core/build.gradle @@ -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') { + implementation('com.opencsv:opencsv:3.9') { exclude group: 'commons-logging', module: 'commons-logging' } - compile 'junit:junit:4.12' } jacocoTestReport { diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Checkmark.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Checkmark.java index 1075d4b4a..79a54d1ed 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Checkmark.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Checkmark.java @@ -56,10 +56,10 @@ public final class Checkmark /** * The value of the checkmark. - * + *

* For boolean habits, this equals either UNCHECKED, CHECKED_EXPLICITLY, * or CHECKED_IMPLICITLY. - * + *

* 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() { diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkList.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkList.java index 847775127..594fd0600 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkList.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkList.java @@ -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 buildCheckmarksFromIntervals(Repetition[] reps, + ArrayList 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 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. + *

+ * 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 buildIntervals(@NonNull Frequency freq, + @NonNull Repetition[] reps) + { + long day = DateUtils.millisecondsInOneDay; + int num = freq.getNumerator(); + int den = freq.getDenominator(); + + ArrayList 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 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. *

@@ -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; } /** @@ -135,7 +238,7 @@ public abstract class CheckmarkList */ public final int[] getValues(long from, long to) { - if(from > to) return new int[0]; + if (from > to) return new int[0]; List checkmarks = getByInterval(from, to); int values[] = new int[checkmarks.size()]; @@ -168,7 +271,7 @@ public abstract class CheckmarkList synchronized (this) { - computeAll(); + compute(); values = getAllValues(); } @@ -184,118 +287,126 @@ 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(); + + Repetition reps[] = habit + .getRepetitions() + .getByInterval(from, today) + .toArray(new Repetition[0]); + + if (habit.isNumerical()) computeNumerical(reps); + else computeYesNo(reps); } /** - * Returns oldest checkmark that has already been computed. + * Returns newest checkmark that has already been computed. * - * @return oldest checkmark already computed + * @return newest checkmark already computed */ @Nullable - protected abstract Checkmark getOldestComputed(); + protected abstract Checkmark getNewestComputed(); /** - * 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. + * Returns oldest checkmark that has already been computed. * - * @param from timestamp for the beginning of the interval - * @param to timestamp for the end of the interval + * @return oldest checkmark already computed */ - private synchronized void forceRecompute(long from, long to) - { - if (from > to) return; + @Nullable + protected abstract Checkmark getOldestComputed(); - final long day = DateUtils.millisecondsInOneDay; - Frequency freq = habit.getFrequency(); + private void computeNumerical(Repetition[] reps) + { + if (reps.length == 0) throw new IllegalArgumentException(); - long fromExtended = from - (long) (freq.getDenominator()) * day; - List reps = - habit.getRepetitions().getByInterval(fromExtended, to); + long day = DateUtils.millisecondsInOneDay; + long today = DateUtils.getStartOfToday(); + long begin = reps[0].getTimestamp(); - final int nDays = (int) ((to - from) / day) + 1; - int nDaysExtended = (int) ((to - fromExtended) / day) + 1; - final int checks[] = new int[nDaysExtended]; + int nDays = (int) ((today - begin) / day) + 1; + List checkmarks = new ArrayList<>(nDays); + for (int i = 0; i < nDays; i++) + checkmarks.add(new Checkmark(today - day * i, 0)); for (Repetition rep : reps) { - int offset = (int) ((rep.getTimestamp() - fromExtended) / day); - checks[nDaysExtended - offset - 1] = rep.getValue(); + long date = rep.getTimestamp(); + int offset = (int) ((today - date) / day); + checkmarks.set(offset, new Checkmark(date, rep.getValue())); } - for (int i = 0; i < nDays; i++) - { - int counter = 0; + add(checkmarks); + } - for (int j = 0; j < freq.getDenominator(); j++) - if (checks[i + j] == CHECKED_EXPLICITLY) counter++; + private void computeYesNo(Repetition[] reps) + { + ArrayList intervals; + intervals = buildIntervals(habit.getFrequency(), reps); + snapIntervalsTogether(intervals); + add(buildCheckmarksFromIntervals(reps, intervals)); + } - if (counter >= freq.getNumerator()) - if (checks[i] != CHECKED_EXPLICITLY) - checks[i] = CHECKED_IMPLICITLY; - } + static class Interval + { + final long begin; - List checkmarks = new LinkedList<>(); + final long center; - for (int i = 0; i < nDays; i++) + final long end; + + Interval(long begin, long center, long end) { - int value = checks[i]; - long timestamp = to - i * day; - checkmarks.add(new Checkmark(timestamp, value)); + this.begin = begin; + this.center = center; + this.end = end; } - add(checkmarks); - } + @Override + public boolean equals(Object o) + { + if (this == o) return true; - /** - * 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; + if (o == null || getClass() != o.getClass()) return false; - Long today = DateUtils.getStartOfToday(); - compute(oldest.getTimestamp(), today); - } + Interval interval = (Interval) o; - /** - * Returns newest checkmark that has already been computed. - * - * @return newest checkmark already computed - */ - @Nullable - protected abstract Checkmark getNewestComputed(); + 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(); + } + } } diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Repetition.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Repetition.java index 3d04328d0..323577588 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Repetition.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Repetition.java @@ -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; diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java index db1346cb7..790e9d945 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java @@ -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 getByInterval(long fromTimestamp, long toTimestamp); diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java index fb33a168a..728ba3548 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java @@ -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 list; + ArrayList 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 getByInterval(long fromTimestamp, long toTimestamp) { - compute(fromTimestamp, toTimestamp); + compute(); - List filtered = new LinkedList<>(); + 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(); - for (Checkmark c : list) - if (c.getTimestamp() >= fromTimestamp && - c.getTimestamp() <= toTimestamp) filtered.add(c); + List filtered = new LinkedList<>(); + 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 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); } } diff --git a/uhabits-core/src/test/java/org/isoron/uhabits/core/models/CheckmarkListTest.java b/uhabits-core/src/test/java/org/isoron/uhabits/core/models/CheckmarkListTest.java index b510d8fad..158807a1e 100644 --- a/uhabits-core/src/test/java/org/isoron/uhabits/core/models/CheckmarkListTest.java +++ b/uhabits-core/src/test/java/org/isoron/uhabits/core/models/CheckmarkListTest.java @@ -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 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 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 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 intervals = new ArrayList<>(); + intervals.add(new CheckmarkList.Interval(day(0), day(0), day(-10))); + + List expected = new ArrayList<>(); + expected.add(new Checkmark(day(0), CHECKED_EXPLICITLY)); + + List 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 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 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 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 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 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 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 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 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 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 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); } } diff --git a/uhabits-core/src/test/java/org/isoron/uhabits/core/models/ScoreListTest.java b/uhabits-core/src/test/java/org/isoron/uhabits/core/models/ScoreListTest.java index 44d5214d3..0d38d8e02 100644 --- a/uhabits-core/src/test/java/org/isoron/uhabits/core/models/ScoreListTest.java +++ b/uhabits-core/src/test/java/org/isoron/uhabits/core/models/ScoreListTest.java @@ -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(); diff --git a/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java b/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java index 6fbef66cf..653b7e843 100644 --- a/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java +++ b/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java @@ -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