From 37a9e793e74b5717913b89433e119e5c2742b7a4 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 23 Jul 2016 18:06:03 -0400 Subject: [PATCH] Add tests for HabitCardListCache; refactor TaskRunners --- .../org/isoron/uhabits/AndroidModule.java | 8 + .../org/isoron/uhabits/models/HabitList.java | 2 +- .../uhabits/tasks/AndroidTaskRunner.java | 120 ++++++++++++ ...ssBar.java => SingleThreadTaskRunner.java} | 37 ++-- .../org/isoron/uhabits/tasks/TaskRunner.java | 95 +--------- .../isoron/uhabits/ui/AndroidProgressBar.java | 67 ------- .../org/isoron/uhabits/ui/BaseScreen.java | 16 -- .../ui/habits/list/ListHabitsActivity.java | 2 - .../list/model/HabitCardListAdapter.java | 6 - .../habits/list/model/HabitCardListCache.java | 52 ++--- .../java/org/isoron/uhabits/BaseUnitTest.java | 4 + .../java/org/isoron/uhabits/TestModule.java | 16 +- .../tasks/SingleThreadTaskRunnerTest.java | 56 ++++++ .../list/model/HabitCardListCacheTest.java | 179 ++++++++++++++++++ 14 files changed, 432 insertions(+), 228 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/tasks/AndroidTaskRunner.java rename app/src/main/java/org/isoron/uhabits/tasks/{ProgressBar.java => SingleThreadTaskRunner.java} (60%) delete mode 100644 app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java create mode 100644 app/src/test/java/org/isoron/uhabits/tasks/SingleThreadTaskRunnerTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCacheTest.java diff --git a/app/src/main/java/org/isoron/uhabits/AndroidModule.java b/app/src/main/java/org/isoron/uhabits/AndroidModule.java index e2d7902e8..48a972469 100644 --- a/app/src/main/java/org/isoron/uhabits/AndroidModule.java +++ b/app/src/main/java/org/isoron/uhabits/AndroidModule.java @@ -23,6 +23,7 @@ import android.content.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.sqlite.*; +import org.isoron.uhabits.tasks.*; import javax.inject.*; @@ -56,4 +57,11 @@ public class AndroidModule { return HabitsApplication.getContext(); } + + @Provides + @Singleton + static TaskRunner provideTaskRunner() + { + return new AndroidTaskRunner(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/HabitList.java b/app/src/main/java/org/isoron/uhabits/models/HabitList.java index ed533c70a..b63cd33e8 100644 --- a/app/src/main/java/org/isoron/uhabits/models/HabitList.java +++ b/app/src/main/java/org/isoron/uhabits/models/HabitList.java @@ -89,7 +89,7 @@ public abstract class HabitList implements Iterable * @return the habit at that position * @throws IndexOutOfBoundsException when the position is invalid */ - @Nullable + @NonNull public abstract Habit getByPosition(int position); /** diff --git a/app/src/main/java/org/isoron/uhabits/tasks/AndroidTaskRunner.java b/app/src/main/java/org/isoron/uhabits/tasks/AndroidTaskRunner.java new file mode 100644 index 000000000..7f39f8b7f --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/tasks/AndroidTaskRunner.java @@ -0,0 +1,120 @@ +/* + * 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.tasks; + +import android.os.*; + +import java.util.*; +import java.util.concurrent.*; + +import javax.inject.*; + +@Singleton +public class AndroidTaskRunner implements TaskRunner +{ + private final LinkedList activeTasks; + + @Inject + public AndroidTaskRunner() + { + activeTasks = new LinkedList<>(); + } + + @Override + public void execute(Task task) + { + task.onAttached(this); + new CustomAsyncTask(task).execute(); + } + + @Override + public void publishProgress(Task task, int progress) + { + for (CustomAsyncTask asyncTask : activeTasks) + if (asyncTask.getTask() == task) asyncTask.publish(progress); + } + + @Override + public void waitForTasks(long timeout) + throws TimeoutException, InterruptedException + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + throw new UnsupportedOperationException("waitForTasks requires API 16+"); + + int poolInterval = 100; + + while(timeout > 0) + { + if(activeTasks.isEmpty()) return; + + timeout -= poolInterval; + Thread.sleep(poolInterval); + } + + throw new TimeoutException(); + } + + private class CustomAsyncTask extends AsyncTask + { + private final Task task; + + public CustomAsyncTask(Task task) + { + this.task = task; + } + + public Task getTask() + { + return task; + } + + public void publish(int progress) + { + publishProgress(progress); + } + + @Override + protected Void doInBackground(Void... params) + { + task.doInBackground(); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) + { + task.onPostExecute(); + activeTasks.remove(this); + } + + @Override + protected void onProgressUpdate(Integer... values) + { + task.onProgressUpdate(values[0]); + } + + @Override + protected void onPreExecute() + { + activeTasks.add(this); + task.onPreExecute(); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java b/app/src/main/java/org/isoron/uhabits/tasks/SingleThreadTaskRunner.java similarity index 60% rename from app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java rename to app/src/main/java/org/isoron/uhabits/tasks/SingleThreadTaskRunner.java index 0be0e4052..34f32d109 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/SingleThreadTaskRunner.java @@ -19,22 +19,29 @@ package org.isoron.uhabits.tasks; -/** - * Simple progress bar, used to indicate the progress of a task. - */ -public interface ProgressBar -{ - /** - * Hides the progress bar. - */ - default void hide() {} +import java.util.concurrent.*; - default void setCurrent(int current) {} +public class SingleThreadTaskRunner implements TaskRunner +{ + @Override + public void execute(Task task) + { + task.onAttached(this); + task.onPreExecute(); + task.doInBackground(); + task.onPostExecute(); + } - default void setTotal(int total) {} + @Override + public void publishProgress(Task task, int progress) + { + task.onProgressUpdate(progress); + } - /** - * Shows the progress bar. - */ - default void show() {} + @Override + public void waitForTasks(long timeout) + throws TimeoutException, InterruptedException + { + // NOP + } } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/TaskRunner.java b/app/src/main/java/org/isoron/uhabits/tasks/TaskRunner.java index 7a121093f..31c59299f 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/TaskRunner.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/TaskRunner.java @@ -19,99 +19,14 @@ package org.isoron.uhabits.tasks; -import android.os.*; - -import java.util.*; import java.util.concurrent.*; -import javax.inject.*; - -@Singleton -public class TaskRunner +public interface TaskRunner { - private final LinkedList activeTasks; - - @Inject - public TaskRunner() - { - activeTasks = new LinkedList<>(); - } - - public void execute(Task task) - { - task.onAttached(this); - new CustomAsyncTask(task).execute(); - } - - public void setCurrentProgress(Task task, int progress) - { - for (CustomAsyncTask asyncTask : activeTasks) - if (asyncTask.getTask() == task) asyncTask.publish(progress); - } - - public void waitForTasks(long timeout) - throws TimeoutException, InterruptedException - { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) - throw new UnsupportedOperationException("waitForTasks requires API 16+"); - - int poolInterval = 100; - - while(timeout > 0) - { - if(activeTasks.isEmpty()) return; - - timeout -= poolInterval; - Thread.sleep(poolInterval); - } - - throw new TimeoutException(); - } - - private class CustomAsyncTask extends AsyncTask - { - private final Task task; - - public CustomAsyncTask(Task task) - { - this.task = task; - } - - public Task getTask() - { - return task; - } - - public void publish(int progress) - { - publishProgress(progress); - } - - @Override - protected Void doInBackground(Void... params) - { - task.doInBackground(); - return null; - } - - @Override - protected void onPostExecute(Void aVoid) - { - task.onPostExecute(); - activeTasks.remove(this); - } + void execute(Task task); - @Override - protected void onProgressUpdate(Integer... values) - { - task.onProgressUpdate(values[0]); - } + void publishProgress(Task task, int progress); - @Override - protected void onPreExecute() - { - activeTasks.add(this); - task.onPreExecute(); - } - } + void waitForTasks(long timeout) + throws TimeoutException, InterruptedException; } diff --git a/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java b/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java deleted file mode 100644 index 07a468893..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java +++ /dev/null @@ -1,67 +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.ui; - -import android.view.*; - -import org.isoron.uhabits.tasks.*; - -/** - * Android implementation of {@link ProgressBar}. - */ -public class AndroidProgressBar implements ProgressBar -{ - private final android.widget.ProgressBar progressBar; - - public AndroidProgressBar(android.widget.ProgressBar progressBar) - { - this.progressBar = progressBar; - } - - @Override - public void hide() - { - progressBar.setVisibility(View.GONE); - } - - @Override - public void setTotal(int total) - { - if(total == 0) - progressBar.setIndeterminate(true); - else - { - progressBar.setIndeterminate(false); - progressBar.setMax(total); - } - } - - @Override - public void setCurrent(int current) - { - progressBar.setProgress(current); - } - - @Override - public void show() - { - progressBar.setVisibility(View.VISIBLE); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java index 5b034a2e0..538e7d67b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java @@ -31,7 +31,6 @@ import android.support.v7.widget.*; import android.view.*; import org.isoron.uhabits.*; -import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.utils.*; import java.io.*; @@ -100,21 +99,6 @@ public abstract class BaseScreen selectionMenu.finish(); } - /** - * Returns the progress bar that is currently visible on the screen. - *

- * If the root view attached to the screen does not provide any progress - * bars, returns null. - * - * @return current progress bar, or null if there are none. - */ - @Nullable - public ProgressBar getProgressBar() - { - if (rootView == null) return null; - return new AndroidProgressBar(rootView.getProgressBar()); - } - /** * Notifies the screen that its contents should be updated. */ diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java index d7814a3df..aa9a3c682 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java @@ -67,8 +67,6 @@ public class ListHabitsActivity extends BaseActivity selectionMenu = new ListHabitsSelectionMenu(habits, screen, adapter); controller = new ListHabitsController(habits, screen, system, adapter); - adapter.setProgressBar( - new AndroidProgressBar(rootView.getProgressBar())); screen.setMenu(menu); screen.setController(controller); screen.setSelectionMenu(selectionMenu); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java index ea998b454..17e2c3a4c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java @@ -25,7 +25,6 @@ import android.view.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.ui.habits.list.views.*; import java.util.*; @@ -254,11 +253,6 @@ public class HabitCardListAdapter this.listView = listView; } - public void setProgressBar(ProgressBar progressBar) - { - cache.setProgressBar(progressBar); - } - /** * Selects or deselects the item at a given position. * diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java index 8a1f20043..639579e42 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java @@ -43,7 +43,7 @@ public class HabitCardListCache implements CommandRunner.Listener private Task currentFetchTask; - @Nullable + @NonNull private Listener listener; @NonNull @@ -63,6 +63,7 @@ public class HabitCardListCache implements CommandRunner.Listener { this.allHabits = allHabits; this.filteredHabits = allHabits; + this.listener = new Listener() {}; data = new CacheData(); BaseComponent component = HabitsApplication.getComponent(); @@ -145,7 +146,7 @@ public class HabitCardListCache implements CommandRunner.Listener data.checkmarks.remove(id); data.scores.remove(id); - if (listener != null) listener.onItemRemoved(position); + listener.onItemRemoved(position); } public void reorder(int from, int to) @@ -153,7 +154,7 @@ public class HabitCardListCache implements CommandRunner.Listener Habit fromHabit = data.habits.get(from); data.habits.remove(from); data.habits.add(to, fromHabit); - if (listener != null) listener.onItemMoved(from, to); + listener.onItemMoved(from, to); } public void setCheckmarkCount(int checkmarkCount) @@ -166,29 +167,24 @@ public class HabitCardListCache implements CommandRunner.Listener filteredHabits = allHabits.getFiltered(matcher); } - public void setListener(@Nullable Listener listener) + public void setListener(@NonNull Listener listener) { this.listener = listener; } - public void setProgressBar(@NonNull ProgressBar progressBar) - { - - } - /** * Interface definition for a callback to be invoked when the data on the * cache has been modified. */ public interface Listener { - void onItemChanged(int position); + default void onItemChanged(int position) {} - void onItemInserted(int position); + default void onItemInserted(int position) {} - void onItemMoved(int oldPosition, int newPosition); + default void onItemMoved(int oldPosition, int newPosition) {} - void onItemRemoved(int position); + default void onItemRemoved(int position) {} } private class CacheData @@ -267,7 +263,7 @@ public class HabitCardListCache implements CommandRunner.Listener isCancelled = false; } - public RefreshTask(Long targetId) + public RefreshTask(long targetId) { newData = new CacheData(); this.targetId = targetId; @@ -286,11 +282,11 @@ public class HabitCardListCache implements CommandRunner.Listener newData.copyScoresFrom(data); newData.copyCheckmarksFrom(data); - long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); long day = DateUtils.millisecondsInOneDay; + long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); long dateFrom = dateTo - (checkmarkCount - 1) * day; - runner.setCurrentProgress(this, -1); + runner.publishProgress(this, -1); for (int position = 0; position < newData.habits.size(); position++) { @@ -304,7 +300,7 @@ public class HabitCardListCache implements CommandRunner.Listener newData.checkmarks.put(id, habit.getCheckmarks().getValues(dateFrom, dateTo)); - runner.setCurrentProgress(this, position); + runner.publishProgress(this, position); } } @@ -334,22 +330,32 @@ public class HabitCardListCache implements CommandRunner.Listener data.id_to_habit.put(id, habit); data.scores.put(id, newData.scores.get(id)); data.checkmarks.put(id, newData.checkmarks.get(id)); - if (listener != null) listener.onItemInserted(position); + listener.onItemInserted(position); } private void performMove(Habit habit, int fromPosition, int toPosition) { data.habits.remove(fromPosition); data.habits.add(toPosition, habit); - if (listener != null) - listener.onItemMoved(fromPosition, toPosition); + listener.onItemMoved(fromPosition, toPosition); } private void performUpdate(Long id, int position) { - data.scores.put(id, newData.scores.get(id)); - data.checkmarks.put(id, newData.checkmarks.get(id)); - if (listener != null) listener.onItemChanged(position); + Integer oldScore = data.scores.get(id); + int[] oldCheckmarks = data.checkmarks.get(id); + + Integer newScore = newData.scores.get(id); + int[] newCheckmarks = newData.checkmarks.get(id); + + boolean unchanged = true; + if (!oldScore.equals(newScore)) unchanged = false; + if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false; + if(unchanged) return; + + data.scores.put(id, newScore); + data.checkmarks.put(id, newCheckmarks); + listener.onItemChanged(position); } private void processPosition(int currentPosition) diff --git a/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java index 0e7c995c3..4ba5e4c59 100644 --- a/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java +++ b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java @@ -19,6 +19,7 @@ package org.isoron.uhabits; +import org.isoron.uhabits.commands.*; import org.isoron.uhabits.intents.*; import org.isoron.uhabits.io.*; import org.isoron.uhabits.models.*; @@ -60,6 +61,9 @@ public class BaseUnitTest @Inject protected DirFinder dirFinder; + @Inject + protected CommandRunner commandRunner; + protected TestComponent testComponent; protected HabitFixtures fixtures; diff --git a/app/src/test/java/org/isoron/uhabits/TestModule.java b/app/src/test/java/org/isoron/uhabits/TestModule.java index 42f9d042f..44a03a62d 100644 --- a/app/src/test/java/org/isoron/uhabits/TestModule.java +++ b/app/src/test/java/org/isoron/uhabits/TestModule.java @@ -19,11 +19,11 @@ package org.isoron.uhabits; -import org.isoron.uhabits.commands.*; import org.isoron.uhabits.intents.*; import org.isoron.uhabits.io.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.memory.*; +import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.ui.common.dialogs.*; import org.isoron.uhabits.utils.*; @@ -36,13 +36,6 @@ import static org.mockito.Mockito.*; @Module public class TestModule { - @Singleton - @Provides - CommandRunner provideCommandRunner() - { - return mock(CommandRunner.class); - } - @Provides @Singleton DialogFactory provideDialogFactory() @@ -125,4 +118,11 @@ public class TestModule { return mock(WidgetPreferences.class); } + + @Provides + @Singleton + TaskRunner provideTaskRunner() + { + return new SingleThreadTaskRunner(); + } } diff --git a/app/src/test/java/org/isoron/uhabits/tasks/SingleThreadTaskRunnerTest.java b/app/src/test/java/org/isoron/uhabits/tasks/SingleThreadTaskRunnerTest.java new file mode 100644 index 000000000..57b694a7b --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/tasks/SingleThreadTaskRunnerTest.java @@ -0,0 +1,56 @@ +/* + * 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.tasks; + +import org.isoron.uhabits.*; +import org.junit.*; +import org.junit.runner.*; +import org.junit.runners.*; +import org.mockito.*; + +import static org.mockito.Mockito.*; + +@RunWith(JUnit4.class) +public class SingleThreadTaskRunnerTest extends BaseUnitTest +{ + private SingleThreadTaskRunner runner; + + private Task task; + + @Override + public void setUp() + { + super.setUp(); + runner = new SingleThreadTaskRunner(); + task = mock(Task.class); + } + + @Test + public void test() + { + runner.execute(task); + + InOrder inOrder = inOrder(task); + inOrder.verify(task).onAttached(runner); + inOrder.verify(task).onPreExecute(); + inOrder.verify(task).doInBackground(); + inOrder.verify(task).onPostExecute(); + } +} diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCacheTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCacheTest.java new file mode 100644 index 000000000..44324f412 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCacheTest.java @@ -0,0 +1,179 @@ +/* + * 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.ui.habits.list.model; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; + +import java.util.*; + +import static junit.framework.Assert.*; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.core.IsEqual.*; +import static org.mockito.Mockito.*; + +public class HabitCardListCacheTest extends BaseUnitTest +{ + private HabitCardListCache cache; + + private HabitCardListCache.Listener listener; + + @Override + public void setUp() + { + super.setUp(); + + fixtures.purgeHabits(); + + for (int i = 0; i < 10; i++) + { + if (i == 3) fixtures.createLongHabit(); + else fixtures.createShortHabit(); + } + + cache = new HabitCardListCache(habitList); + cache.setCheckmarkCount(10); + cache.refreshAllHabits(); + cache.onAttached(); + + listener = mock(HabitCardListCache.Listener.class); + cache.setListener(listener); + } + + @Override + public void tearDown() + { + cache.onDetached(); + super.tearDown(); + } + + @Test + public void testCommandListener_all() + { + assertThat(cache.getHabitCount(), equalTo(10)); + + Habit h = habitList.getByPosition(0); + commandRunner.execute( + new DeleteHabitsCommand(habitList, Collections.singletonList(h)), + null); + + verify(listener).onItemRemoved(0); + assertThat(cache.getHabitCount(), equalTo(9)); + } + + @Test + public void testCommandListener_single() + { + Habit h2 = habitList.getByPosition(2); + long today = DateUtils.getStartOfToday(); + commandRunner.execute(new ToggleRepetitionCommand(h2, today), + h2.getId()); + + verify(listener).onItemChanged(2); + verifyNoMoreInteractions(listener); + } + + @Test + public void testGet() + { + assertThat(cache.getHabitCount(), equalTo(10)); + + Habit h = habitList.getByPosition(3); + assertNotNull(h.getId()); + int score = h.getScores().getTodayValue(); + + assertThat(cache.getHabitByPosition(3), equalTo(h)); + assertThat(cache.getScore(h.getId()), equalTo(score)); + + long today = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; + + int[] actualCheckmarks = cache.getCheckmarks(h.getId()); + int[] expectedCheckmarks = + h.getCheckmarks().getValues(today - 9 * day, today); + + assertThat(actualCheckmarks, equalTo(expectedCheckmarks)); + } + + @Test + public void testRemoval() + { + removeHabitAt(0); + removeHabitAt(3); + + cache.refreshAllHabits(); + verify(listener).onItemRemoved(0); + verify(listener).onItemRemoved(3); + assertThat(cache.getHabitCount(), equalTo(8)); + } + + @Test + public void testReorder_onCache() + { + Habit h2 = cache.getHabitByPosition(2); + Habit h3 = cache.getHabitByPosition(3); + Habit h7 = cache.getHabitByPosition(7); + + cache.reorder(2, 7); + + assertThat(cache.getHabitByPosition(2), equalTo(h3)); + assertThat(cache.getHabitByPosition(7), equalTo(h2)); + assertThat(cache.getHabitByPosition(6), equalTo(h7)); + verify(listener).onItemMoved(2, 7); + verifyNoMoreInteractions(listener); + } + + @Test + public void testReorder_onList() + { + Habit h2 = habitList.getByPosition(2); + Habit h3 = habitList.getByPosition(3); + Habit h7 = habitList.getByPosition(7); + + assertThat(cache.getHabitByPosition(2), equalTo(h2)); + assertThat(cache.getHabitByPosition(7), equalTo(h7)); + reset(listener); + + habitList.reorder(h2, h7); + cache.refreshAllHabits(); + + assertThat(cache.getHabitByPosition(2), equalTo(h3)); + assertThat(cache.getHabitByPosition(7), equalTo(h2)); + assertThat(cache.getHabitByPosition(6), equalTo(h7)); + + verify(listener).onItemMoved(3, 2); + verify(listener).onItemMoved(4, 3); + verify(listener).onItemMoved(5, 4); + verify(listener).onItemMoved(6, 5); + verify(listener).onItemMoved(7, 6); + verifyNoMoreInteractions(listener); + } + + protected void removeHabitAt(int position) + { + Habit h = habitList.getByPosition(position); + assertNotNull(h); + habitList.remove(h); + } + +} \ No newline at end of file