Implement alternative checkmark algorithm
Fix failing tests
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
find app/build/outputs/failed/test-screenshots -name '*.expected*' -delete
|
find uhabits-android/build/outputs/failed/test-screenshots -name '*.expected*' -delete
|
||||||
rsync -av app/build/outputs/failed/test-screenshots/ app/src/androidTest/assets/
|
rsync -av uhabits-android/build/outputs/failed/test-screenshots/ uhabits-android/src/androidTest/assets/
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||||
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.4'
|
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.4'
|
||||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
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.auto.factory:auto-factory:1.0-beta3'
|
||||||
testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.9'
|
testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.9'
|
||||||
testAnnotationProcessor 'com.jakewharton:butterknife-compiler:8.6.1-SNAPSHOT'
|
testAnnotationProcessor 'com.jakewharton:butterknife-compiler:8.6.1-SNAPSHOT'
|
||||||
testCompile 'com.google.dagger:dagger:2.9'
|
testImplementation 'com.google.dagger:dagger:2.9'
|
||||||
testCompile "org.mockito:mockito-core:2.8.9"
|
testImplementation "org.mockito:mockito-core:2.8.9"
|
||||||
testCompile "org.mockito:mockito-inline:2.8.9"
|
testImplementation "org.mockito:mockito-inline:2.8.9"
|
||||||
|
testImplementation 'junit:junit:4+'
|
||||||
|
|
||||||
implementation('com.opencsv:opencsv:3.9') {
|
implementation('com.opencsv:opencsv:3.9') {
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
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;
|
package org.isoron.uhabits.activities.habits.list;
|
||||||
|
|
||||||
import android.content.*;
|
|
||||||
import android.os.*;
|
import android.os.*;
|
||||||
|
|
||||||
import org.isoron.androidbase.activities.*;
|
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.preferences.*;
|
||||||
import org.isoron.uhabits.core.ui.*;
|
import org.isoron.uhabits.core.ui.*;
|
||||||
import org.isoron.uhabits.core.utils.*;
|
import org.isoron.uhabits.core.utils.*;
|
||||||
import org.isoron.uhabits.sync.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity that allows the user to see and modify the list of habits.
|
* 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);
|
screen.setSelectionMenu(selectionMenu);
|
||||||
rootView.setController(controller, selectionMenu);
|
rootView.setController(controller, selectionMenu);
|
||||||
|
|
||||||
if(prefs.isSyncEnabled())
|
// if(prefs.isSyncEnabled())
|
||||||
startService(new Intent(this, SyncService.class));
|
// startService(new Intent(this, SyncService.class));
|
||||||
|
|
||||||
setScreen(screen);
|
setScreen(screen);
|
||||||
controller.onStartup();
|
controller.onStartup();
|
||||||
|
|||||||
@@ -188,9 +188,6 @@ public class SQLiteHabitList extends HabitList
|
|||||||
@Override
|
@Override
|
||||||
public void removeAll()
|
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 repetitions", null);
|
||||||
sqlite.query("delete from habits", null);
|
sqlite.query("delete from habits", null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,29 +6,23 @@ apply plugin: 'java'
|
|||||||
apply plugin: 'jacoco'
|
apply plugin: 'jacoco'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
apt 'com.google.auto.factory:auto-factory:1.0-beta3'
|
apt 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||||
apt 'com.google.dagger:dagger:2.11-rc2'
|
apt 'com.google.dagger:dagger:2.11-rc2'
|
||||||
|
|
||||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||||
compileOnly 'com.google.auto.factory:auto-factory:1.0-beta3'
|
compileOnly 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||||
compileOnly 'com.google.dagger:dagger:2.11-rc2'
|
compileOnly 'com.google.dagger:dagger:2.11-rc2'
|
||||||
|
|
||||||
implementation 'com.android.support:support-annotations:25.3.1'
|
implementation 'com.android.support:support-annotations:25.3.1'
|
||||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||||
implementation 'org.apache.commons:commons-lang3:3.5'
|
implementation 'org.apache.commons:commons-lang3:3.5'
|
||||||
implementation 'com.google.code.gson:gson:2.7'
|
implementation 'com.google.code.gson:gson:2.7'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4+'
|
testImplementation 'junit:junit:4+'
|
||||||
testImplementation 'org.hamcrest:hamcrest-library:1.4-atlassian-1'
|
testImplementation 'org.hamcrest:hamcrest-library:1.4-atlassian-1'
|
||||||
testImplementation 'org.apache.commons:commons-io:1.3.2'
|
testImplementation 'org.apache.commons:commons-io:1.3.2'
|
||||||
testImplementation 'org.mockito:mockito-core:2.8.9'
|
testImplementation 'org.mockito:mockito-core:2.8.9'
|
||||||
testImplementation 'org.json:json:20160810'
|
testImplementation 'org.json:json:20160810'
|
||||||
|
implementation('com.opencsv:opencsv:3.9') {
|
||||||
implementation ('com.opencsv:opencsv:3.9') {
|
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
}
|
}
|
||||||
compile 'junit:junit:4.12'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jacocoTestReport {
|
jacocoTestReport {
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ public final class Checkmark
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The value of the checkmark.
|
* The value of the checkmark.
|
||||||
*
|
* <p>
|
||||||
* For boolean habits, this equals either UNCHECKED, CHECKED_EXPLICITLY,
|
* For boolean habits, this equals either UNCHECKED, CHECKED_EXPLICITLY,
|
||||||
* or CHECKED_IMPLICITLY.
|
* or CHECKED_IMPLICITLY.
|
||||||
*
|
* <p>
|
||||||
* For numerical habits, this number is stored in thousandths. That
|
* 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 stored as 1500.
|
||||||
*/
|
*/
|
||||||
@@ -76,6 +76,21 @@ public final class Checkmark
|
|||||||
return Long.signum(this.getTimestamp() - other.getTimestamp());
|
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()
|
public long getTimestamp()
|
||||||
{
|
{
|
||||||
return timestamp;
|
return timestamp;
|
||||||
@@ -86,6 +101,15 @@ public final class Checkmark
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return new HashCodeBuilder(17, 37)
|
||||||
|
.append(timestamp)
|
||||||
|
.append(value)
|
||||||
|
.toHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.core.models;
|
|||||||
|
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.builder.*;
|
||||||
import org.isoron.uhabits.core.utils.*;
|
import org.isoron.uhabits.core.utils.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@@ -29,7 +30,10 @@ import java.util.*;
|
|||||||
|
|
||||||
import javax.annotation.concurrent.*;
|
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.
|
* The collection of {@link Checkmark}s belonging to a habit.
|
||||||
*/
|
*/
|
||||||
@@ -46,6 +50,104 @@ public abstract class CheckmarkList
|
|||||||
this.observable = new ModelObservable();
|
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.
|
* Adds all the given checkmarks to the list.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -104,8 +206,9 @@ public abstract class CheckmarkList
|
|||||||
@Nullable
|
@Nullable
|
||||||
public synchronized final Checkmark getToday()
|
public synchronized final Checkmark getToday()
|
||||||
{
|
{
|
||||||
computeAll();
|
compute();
|
||||||
return getNewestComputed();
|
long today = DateUtils.getStartOfToday();
|
||||||
|
return getByInterval(today, today).get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,7 +220,7 @@ public abstract class CheckmarkList
|
|||||||
{
|
{
|
||||||
Checkmark today = getToday();
|
Checkmark today = getToday();
|
||||||
if (today != null) return today.getValue();
|
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)
|
public final int[] getValues(long from, long to)
|
||||||
{
|
{
|
||||||
if(from > to) return new int[0];
|
if (from > to) return new int[0];
|
||||||
|
|
||||||
List<Checkmark> checkmarks = getByInterval(from, to);
|
List<Checkmark> checkmarks = getByInterval(from, to);
|
||||||
int values[] = new int[checkmarks.size()];
|
int values[] = new int[checkmarks.size()];
|
||||||
@@ -168,7 +271,7 @@ public abstract class CheckmarkList
|
|||||||
|
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
computeAll();
|
compute();
|
||||||
values = getAllValues();
|
values = getAllValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,111 +287,28 @@ public abstract class CheckmarkList
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes and stores one checkmark for each day that falls inside the
|
* Computes and stores one checkmark for each day, from the first habit
|
||||||
* specified interval of time. Days that already have a corresponding
|
* repetition to today. If this list is already computed, does nothing.
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
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 newest = getNewestComputed();
|
||||||
Checkmark oldest = getOldestComputed();
|
if(newest != null && newest.getTimestamp() == today) return;
|
||||||
|
invalidateNewerThan(0);
|
||||||
|
|
||||||
if (newest == null || oldest == null)
|
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||||
{
|
if (oldestRep == null) return;
|
||||||
forceRecompute(from, to);
|
final long from = oldestRep.getTimestamp();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
forceRecompute(from, oldest.getTimestamp() - day);
|
|
||||||
forceRecompute(newest.getTimestamp() + day, to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Repetition reps[] = habit
|
||||||
* Returns oldest checkmark that has already been computed.
|
.getRepetitions()
|
||||||
*
|
.getByInterval(from, today)
|
||||||
* @return oldest checkmark already computed
|
.toArray(new Repetition[0]);
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
protected abstract Checkmark getOldestComputed();
|
|
||||||
|
|
||||||
/**
|
if (habit.isNumerical()) computeNumerical(reps);
|
||||||
* Computes and stores one checkmark for each day that falls inside the
|
else computeYesNo(reps);
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -298,4 +318,95 @@ public abstract class CheckmarkList
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
protected abstract Checkmark getNewestComputed();
|
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.
|
* The value of the repetition.
|
||||||
*
|
*
|
||||||
* For boolean habits, this equals either Checkmark.UNCHECKED,
|
* For boolean habits, this always equals Checkmark.CHECKED_EXPLICITLY.
|
||||||
* Checkmark.CHECKED_EXPLICITLY, or Checkmark.CHECKED_IMPLICITLY.
|
|
||||||
*
|
|
||||||
* For numerical habits, this number is stored in thousandths. That
|
* 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;
|
private final int value;
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ public abstract class RepetitionList
|
|||||||
* @param toTimestamp timestamp of the end of the interval
|
* @param toTimestamp timestamp of the end of the interval
|
||||||
* @return list of repetitions within given time interval
|
* @return list of repetitions within given time interval
|
||||||
*/
|
*/
|
||||||
// TODO: Change order timestamp desc
|
|
||||||
public abstract List<Repetition> getByInterval(long fromTimestamp,
|
public abstract List<Repetition> getByInterval(long fromTimestamp,
|
||||||
long toTimestamp);
|
long toTimestamp);
|
||||||
|
|
||||||
|
|||||||
@@ -25,17 +25,19 @@ import org.isoron.uhabits.core.models.*;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.core.utils.DateUtils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In-memory implementation of {@link CheckmarkList}.
|
* In-memory implementation of {@link CheckmarkList}.
|
||||||
*/
|
*/
|
||||||
public class MemoryCheckmarkList extends CheckmarkList
|
public class MemoryCheckmarkList extends CheckmarkList
|
||||||
{
|
{
|
||||||
LinkedList<Checkmark> list;
|
ArrayList<Checkmark> list;
|
||||||
|
|
||||||
public MemoryCheckmarkList(Habit habit)
|
public MemoryCheckmarkList(Habit habit)
|
||||||
{
|
{
|
||||||
super(habit);
|
super(habit);
|
||||||
list = new LinkedList<>();
|
list = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,13 +51,27 @@ public class MemoryCheckmarkList extends CheckmarkList
|
|||||||
@Override
|
@Override
|
||||||
public List<Checkmark> getByInterval(long fromTimestamp, long toTimestamp)
|
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<>();
|
List<Checkmark> filtered = new LinkedList<>();
|
||||||
|
for(long time = toTimestamp; time >= fromTimestamp; time -= millisecondsInOneDay)
|
||||||
for (Checkmark c : list)
|
{
|
||||||
if (c.getTimestamp() >= fromTimestamp &&
|
if(time > newestTimestamp || time < oldestTimestamp)
|
||||||
c.getTimestamp() <= toTimestamp) filtered.add(c);
|
filtered.add(new Checkmark(time, Checkmark.UNCHECKED));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int offset = (int) ((newestTimestamp - time) / millisecondsInOneDay);
|
||||||
|
filtered.add(list.get(offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
@@ -63,12 +79,8 @@ public class MemoryCheckmarkList extends CheckmarkList
|
|||||||
@Override
|
@Override
|
||||||
public void invalidateNewerThan(long timestamp)
|
public void invalidateNewerThan(long timestamp)
|
||||||
{
|
{
|
||||||
LinkedList<Checkmark> invalid = new LinkedList<>();
|
list.clear();
|
||||||
|
observable.notifyListeners();
|
||||||
for (Checkmark c : list)
|
|
||||||
if (c.getTimestamp() >= timestamp) invalid.add(c);
|
|
||||||
|
|
||||||
list.removeAll(invalid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -76,7 +88,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
|||||||
protected Checkmark getOldestComputed()
|
protected Checkmark getOldestComputed()
|
||||||
{
|
{
|
||||||
if(list.isEmpty()) return null;
|
if(list.isEmpty()) return null;
|
||||||
return list.getLast();
|
return list.get(list.size()-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -84,7 +96,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
|||||||
protected Checkmark getNewestComputed()
|
protected Checkmark getNewestComputed()
|
||||||
{
|
{
|
||||||
if(list.isEmpty()) return null;
|
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 org.junit.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.*;
|
import static org.hamcrest.MatcherAssert.*;
|
||||||
import static org.hamcrest.core.IsEqual.*;
|
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
|
public class CheckmarkListTest extends BaseUnitTest
|
||||||
{
|
{
|
||||||
// 8:00am, January 25th, 2015 (UTC)
|
private long dayLength;
|
||||||
private long fixed_local_time = 1422172800000L;
|
|
||||||
|
private long today;
|
||||||
|
|
||||||
private Habit nonDailyHabit;
|
private Habit nonDailyHabit;
|
||||||
|
|
||||||
private Habit emptyHabit;
|
private Habit emptyHabit;
|
||||||
|
|
||||||
|
private Habit numericalHabit;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
DateUtils.setFixedLocalTime(fixed_local_time);
|
dayLength = DateUtils.millisecondsInOneDay;
|
||||||
|
today = DateUtils.getStartOfToday();
|
||||||
|
|
||||||
fixtures.createShortHabit();
|
|
||||||
nonDailyHabit = fixtures.createShortHabit();
|
nonDailyHabit = fixtures.createShortHabit();
|
||||||
habitList.add(nonDailyHabit);
|
habitList.add(nonDailyHabit);
|
||||||
|
|
||||||
emptyHabit = fixtures.createEmptyHabit();
|
emptyHabit = fixtures.createEmptyHabit();
|
||||||
habitList.add(emptyHabit);
|
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
|
@Test
|
||||||
@@ -62,7 +182,7 @@ public class CheckmarkListTest extends BaseUnitTest
|
|||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
UNCHECKED,
|
CHECKED_IMPLICITLY,
|
||||||
CHECKED_IMPLICITLY,
|
CHECKED_IMPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY
|
CHECKED_EXPLICITLY
|
||||||
@@ -73,7 +193,6 @@ public class CheckmarkListTest extends BaseUnitTest
|
|||||||
assertThat(actualValues, equalTo(expectedValues));
|
assertThat(actualValues, equalTo(expectedValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_getAllValues_moveForwardInTime()
|
public void test_getAllValues_moveForwardInTime()
|
||||||
{
|
{
|
||||||
@@ -89,7 +208,7 @@ public class CheckmarkListTest extends BaseUnitTest
|
|||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
UNCHECKED,
|
CHECKED_IMPLICITLY,
|
||||||
CHECKED_IMPLICITLY,
|
CHECKED_IMPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY
|
CHECKED_EXPLICITLY
|
||||||
@@ -119,7 +238,7 @@ public class CheckmarkListTest extends BaseUnitTest
|
|||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
UNCHECKED,
|
CHECKED_IMPLICITLY,
|
||||||
CHECKED_IMPLICITLY,
|
CHECKED_IMPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY
|
CHECKED_EXPLICITLY
|
||||||
@@ -130,20 +249,33 @@ public class CheckmarkListTest extends BaseUnitTest
|
|||||||
assertThat(actualValues, equalTo(expectedValues));
|
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
|
@Test
|
||||||
public void test_getTodayValue()
|
public void test_getTodayValue()
|
||||||
{
|
{
|
||||||
|
CheckmarkList checkmarks = nonDailyHabit.getCheckmarks();
|
||||||
|
|
||||||
travelInTime(-1);
|
travelInTime(-1);
|
||||||
assertThat(nonDailyHabit.getCheckmarks().getTodayValue(),
|
assertThat(checkmarks.getTodayValue(), equalTo(UNCHECKED));
|
||||||
equalTo(UNCHECKED));
|
|
||||||
|
|
||||||
travelInTime(0);
|
travelInTime(0);
|
||||||
assertThat(nonDailyHabit.getCheckmarks().getTodayValue(),
|
assertThat(checkmarks.getTodayValue(), equalTo(CHECKED_EXPLICITLY));
|
||||||
equalTo(CHECKED_EXPLICITLY));
|
|
||||||
|
|
||||||
travelInTime(1);
|
travelInTime(1);
|
||||||
assertThat(nonDailyHabit.getCheckmarks().getTodayValue(),
|
assertThat(checkmarks.getTodayValue(), equalTo(UNCHECKED));
|
||||||
equalTo(UNCHECKED));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -156,14 +288,12 @@ public class CheckmarkListTest extends BaseUnitTest
|
|||||||
@Test
|
@Test
|
||||||
public void test_getValues_withValidInterval()
|
public void test_getValues_withValidInterval()
|
||||||
{
|
{
|
||||||
long from =
|
long from = today - 15 * dayLength;
|
||||||
DateUtils.getStartOfToday() - 15 * DateUtils.millisecondsInOneDay;
|
long to = today - 5 * dayLength;
|
||||||
long to =
|
|
||||||
DateUtils.getStartOfToday() - 5 * DateUtils.millisecondsInOneDay;
|
|
||||||
|
|
||||||
int[] expectedValues = {
|
int[] expectedValues = {
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
UNCHECKED,
|
CHECKED_IMPLICITLY,
|
||||||
CHECKED_IMPLICITLY,
|
CHECKED_IMPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
CHECKED_EXPLICITLY,
|
CHECKED_EXPLICITLY,
|
||||||
@@ -180,18 +310,31 @@ public class CheckmarkListTest extends BaseUnitTest
|
|||||||
assertThat(actualValues, equalTo(expectedValues));
|
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
|
@Test
|
||||||
public void test_writeCSV() throws IOException
|
public void test_writeCSV() throws IOException
|
||||||
{
|
{
|
||||||
String expectedCSV = "2015-01-25,2\n" +
|
String expectedCSV = "2015-01-25,2\n2015-01-24,0\n2015-01-23,1\n" +
|
||||||
"2015-01-24,0\n" +
|
"2015-01-22,2\n2015-01-21,2\n2015-01-20,2\n" +
|
||||||
"2015-01-23,1\n" +
|
"2015-01-19,1\n2015-01-18,1\n2015-01-17,2\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" +
|
|
||||||
"2015-01-16,2\n";
|
"2015-01-16,2\n";
|
||||||
|
|
||||||
|
|
||||||
@@ -201,9 +344,15 @@ public class CheckmarkListTest extends BaseUnitTest
|
|||||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
assertThat(writer.toString(), equalTo(expectedCSV));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long day(int offset)
|
||||||
|
{
|
||||||
|
return DateUtils.getStartOfToday() -
|
||||||
|
offset * DateUtils.millisecondsInOneDay;
|
||||||
|
}
|
||||||
|
|
||||||
private void travelInTime(int days)
|
private void travelInTime(int days)
|
||||||
{
|
{
|
||||||
DateUtils.setFixedLocalTime(
|
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
|
@Test
|
||||||
public void test_groupBy()
|
public void test_groupBy()
|
||||||
{
|
{
|
||||||
@@ -133,9 +155,9 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
habit.getScores().groupBy(DateUtils.TruncateField.MONTH);
|
habit.getScores().groupBy(DateUtils.TruncateField.MONTH);
|
||||||
|
|
||||||
assertThat(list.size(), equalTo(5));
|
assertThat(list.size(), equalTo(5));
|
||||||
assertThat(list.get(0).getValue(), closeTo(0.549096, E));
|
assertThat(list.get(0).getValue(), closeTo(0.653659, E));
|
||||||
assertThat(list.get(1).getValue(), closeTo(0.480098, E));
|
assertThat(list.get(1).getValue(), closeTo(0.622715, E));
|
||||||
assertThat(list.get(2).getValue(), closeTo(0.377885, E));
|
assertThat(list.get(2).getValue(), closeTo(0.520997, E));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -157,16 +179,11 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
{
|
{
|
||||||
Habit habit = fixtures.createShortHabit();
|
Habit habit = fixtures.createShortHabit();
|
||||||
|
|
||||||
String expectedCSV = "2015-01-25,0.2372\n" +
|
String expectedCSV = "2015-01-25,0.2654\n2015-01-24,0.2389\n" +
|
||||||
"2015-01-24,0.2096\n" +
|
"2015-01-23,0.2475\n2015-01-22,0.2203\n" +
|
||||||
"2015-01-23,0.2172\n" +
|
"2015-01-21,0.1921\n2015-01-20,0.1628\n" +
|
||||||
"2015-01-22,0.1889\n" +
|
"2015-01-19,0.1325\n2015-01-18,0.1011\n" +
|
||||||
"2015-01-21,0.1595\n" +
|
"2015-01-17,0.0686\n2015-01-16,0.0349\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";
|
|
||||||
|
|
||||||
StringWriter writer = new StringWriter();
|
StringWriter writer = new StringWriter();
|
||||||
habit.getScores().writeCSV(writer);
|
habit.getScores().writeCSV(writer);
|
||||||
@@ -174,30 +191,6 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
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)
|
private void toggleRepetitions(final int from, final int to)
|
||||||
{
|
{
|
||||||
RepetitionList reps = habit.getRepetitions();
|
RepetitionList reps = habit.getRepetitions();
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ public class ListHabitsBehaviorTest extends BaseUnitTest
|
|||||||
when(system.getCSVOutputDir()).thenReturn(outputDir);
|
when(system.getCSVOutputDir()).thenReturn(outputDir);
|
||||||
behavior.onExportCSV();
|
behavior.onExportCSV();
|
||||||
verify(screen).showMessage(COULD_NOT_EXPORT);
|
verify(screen).showMessage(COULD_NOT_EXPORT);
|
||||||
|
assertTrue(outputDir.delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||