From 9e410f839582070986a196b20610cb942870ae3e Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 25 Mar 2016 06:15:53 -0400 Subject: [PATCH] Refactor and write tests for IO tasks --- app/src/androidTest/assets/icon.png | Bin 0 -> 49120 bytes .../isoron/uhabits/unit/io/ImportTest.java | 6 +- .../uhabits/unit/tasks/ExportCSVTaskTest.java | 77 +++++++++++++ .../uhabits/unit/tasks/ExportDBTaskTest.java | 71 ++++++++++++ .../unit/tasks/ImportDataTaskTest.java | 108 ++++++++++++++++++ .../uhabits/dialogs/FilePickerDialog.java | 8 +- .../uhabits/fragments/ListHabitsFragment.java | 49 +++++++- .../uhabits/helpers/DatabaseHelper.java | 5 +- .../isoron/uhabits/tasks/ExportCSVTask.java | 40 +++---- .../isoron/uhabits/tasks/ExportDBTask.java | 37 +++--- app/src/main/res/values/strings.xml | 12 +- 11 files changed, 349 insertions(+), 64 deletions(-) create mode 100644 app/src/androidTest/assets/icon.png create mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java create mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java create mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java diff --git a/app/src/androidTest/assets/icon.png b/app/src/androidTest/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7907954db759e36b4bacfb99e9c15b9266083b0a GIT binary patch literal 49120 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 + * + * 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.unit.tasks; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.widget.ProgressBar; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.ExportCSVTask; +import org.isoron.uhabits.unit.models.HabitFixtures; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static junit.framework.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.core.IsNot.not; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ExportCSVTaskTest +{ + @Test + public void exportCSV() throws InterruptedException + { + Context context = InstrumentationRegistry.getContext(); + final CountDownLatch latch = new CountDownLatch(1); + + HabitFixtures.createNonDailyHabit(); + List habits = Habit.getAll(true); + ProgressBar bar = new ProgressBar(context); + + ExportCSVTask task = new ExportCSVTask(habits, bar); + task.setListener(new ExportCSVTask.Listener() + { + @Override + public void onExportCSVFinished(String archiveFilename) + { + assertThat(archiveFilename, is(not(nullValue()))); + + File f = new File(archiveFilename); + assertTrue(f.exists()); + assertTrue(f.canRead()); + latch.countDown(); + } + }); + + task.execute(); + latch.await(30, TimeUnit.SECONDS); + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java new file mode 100644 index 000000000..e744b5ede --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java @@ -0,0 +1,71 @@ +/* + * 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.unit.tasks; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.widget.ProgressBar; + +import org.isoron.uhabits.tasks.ExportDBTask; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static junit.framework.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.core.IsNot.not; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ExportDBTaskTest +{ + @Test + public void exportCSV() throws InterruptedException + { + Context context = InstrumentationRegistry.getContext(); + final CountDownLatch latch = new CountDownLatch(1); + + ProgressBar bar = new ProgressBar(context); + ExportDBTask task = new ExportDBTask(bar); + task.setListener(new ExportDBTask.Listener() + { + @Override + public void onExportDBFinished(String filename) + { + assertThat(filename, is(not(nullValue()))); + + File f = new File(filename); + assertTrue(f.exists()); + assertTrue(f.canRead()); + latch.countDown(); + } + }); + + task.execute(); + latch.await(30, TimeUnit.SECONDS); + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java new file mode 100644 index 000000000..dc3f0681a --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java @@ -0,0 +1,108 @@ +/* + * 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.unit.tasks; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.widget.ProgressBar; + +import org.isoron.uhabits.helpers.DatabaseHelper; +import org.isoron.uhabits.tasks.ImportDataTask; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ImportDataTaskTest +{ + private Context context; + private File baseDir; + + @Before + public void setup() + { + context = InstrumentationRegistry.getContext(); + + baseDir = DatabaseHelper.getFilesDir("Backups"); + if(baseDir == null) fail("baseDir should not be null"); + } + + private void copyAssetToFile(String assetPath, File dst) throws IOException + { + InputStream in = context.getAssets().open(assetPath); + DatabaseHelper.copy(in, dst); + } + + private void assertTaskResult(final int expectedResult, String assetFilename) + throws IOException, InterruptedException + { + final CountDownLatch latch = new CountDownLatch(1); + ImportDataTask task = createTask(assetFilename); + + task.setListener(new ImportDataTask.Listener() + { + @Override + public void onImportFinished(int result) + { + assertThat(result, equalTo(expectedResult)); + latch.countDown(); + } + }); + + task.execute(); + latch.await(30, TimeUnit.SECONDS); + } + + @NonNull + private ImportDataTask createTask(String assetFilename) throws IOException + { + ProgressBar bar = new ProgressBar(context); + File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename)); + copyAssetToFile(assetFilename, file); + + return new ImportDataTask(file, bar); + } + + @Test + public void importInvalidData() throws Throwable + { + assertTaskResult(ImportDataTask.NOT_RECOGNIZED, "icon.png"); + } + + @Test + public void importValidData() throws Throwable + { + assertTaskResult(ImportDataTask.SUCCESS, "loop.db"); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java b/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java index a7a8520fd..94c574fbd 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java @@ -48,7 +48,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener void onFileSelected(File file); } - private OnFileSelectedListener fileListener; + private OnFileSelectedListener listener; public FilePickerDialog(Activity activity, File initialDirectory) { @@ -81,7 +81,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener } else { - if (fileListener != null) fileListener.onFileSelected(file); + if (listener != null) listener.onFileSelected(file); dialog.dismiss(); } } @@ -91,9 +91,9 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener dialog.show(); } - public void setFileListener(OnFileSelectedListener fileListener) + public void setListener(OnFileSelectedListener listener) { - this.fileListener = fileListener; + this.listener = listener; } private void navigateTo(File path) diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java index 22a0be019..02ca46b92 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java @@ -26,6 +26,7 @@ import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.annotation.Nullable; import android.view.ActionMode; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -73,7 +74,8 @@ import java.util.List; public class ListHabitsFragment extends Fragment implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener, OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener, - HabitSelectionCallback.Listener, ImportDataTask.Listener + HabitSelectionCallback.Listener, ImportDataTask.Listener, ExportCSVTask.Listener, + ExportDBTask.Listener { long lastLongClick = 0; private boolean isShortToggleEnabled; @@ -437,7 +439,7 @@ public class ListHabitsFragment extends Fragment if(dir == null) return; FilePickerDialog picker = new FilePickerDialog(activity, dir); - picker.setFileListener(new FilePickerDialog.OnFileSelectedListener() + picker.setListener(new FilePickerDialog.OnFileSelectedListener() { @Override public void onFileSelected(File file) @@ -447,6 +449,7 @@ public class ListHabitsFragment extends Fragment task.execute(); } }); + picker.show(); } @@ -472,11 +475,49 @@ public class ListHabitsFragment extends Fragment public void exportAllHabits() { - new ExportCSVTask(activity, Habit.getAll(true), progressBar).execute(); + ExportCSVTask task = new ExportCSVTask(Habit.getAll(true), progressBar); + task.setListener(this); + task.execute(); + } + + @Override + public void onExportCSVFinished(@Nullable String archiveFilename) + { + if(archiveFilename != null) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("application/zip"); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); + activity.startActivity(intent); + } + else + { + activity.showToast(R.string.could_not_export); + } } public void exportDB() { - new ExportDBTask(activity, progressBar).execute(); + ExportDBTask task = new ExportDBTask(progressBar); + task.setListener(this); + task.execute(); + } + + @Override + public void onExportDBFinished(@Nullable String filename) + { + if(filename != null) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("application/octet-stream"); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename))); + activity.startActivity(intent); + } + else + { + activity.showToast(R.string.could_not_export); + } } } diff --git a/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java index cde9ca9f2..cd411328f 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java @@ -124,8 +124,11 @@ public class DatabaseHelper } @Nullable - public static File getFilesDir(Context context, String prefix) + public static File getFilesDir(String prefix) { + Context context = HabitsApplication.getContext(); + if(context == null) return null; + File chosenDir = null; File externalFilesDirs[] = ContextCompat.getExternalFilesDirs(context, null); if(externalFilesDirs == null) return null; diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java index 151abb242..2ebd05cc8 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java @@ -19,14 +19,11 @@ package org.isoron.uhabits.tasks; -import android.content.Intent; -import android.net.Uri; import android.os.AsyncTask; +import android.support.annotation.Nullable; import android.view.View; import android.widget.ProgressBar; -import org.isoron.uhabits.R; -import org.isoron.uhabits.ReplayableActivity; import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.models.Habit; @@ -37,17 +34,25 @@ import java.util.List; public class ExportCSVTask extends AsyncTask { - private final ReplayableActivity activity; + public interface Listener + { + void onExportCSVFinished(@Nullable String archiveFilename); + } + private ProgressBar progressBar; private final List selectedHabits; - String archiveFilename; + private String archiveFilename; + private ExportCSVTask.Listener listener; - public ExportCSVTask(ReplayableActivity activity, List selectedHabits, - ProgressBar progressBar) + public ExportCSVTask(List selectedHabits, ProgressBar progressBar) { this.selectedHabits = selectedHabits; this.progressBar = progressBar; - this.activity = activity; + } + + public void setListener(Listener listener) + { + this.listener = listener; } @Override @@ -63,19 +68,8 @@ public class ExportCSVTask extends AsyncTask @Override protected void onPostExecute(Void aVoid) { - if(archiveFilename != null) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); - - activity.startActivity(intent); - } - else - { - activity.showToast(R.string.could_not_export); - } + if(listener != null) + listener.onExportCSVFinished(archiveFilename); if(progressBar != null) progressBar.setVisibility(View.GONE); @@ -86,7 +80,7 @@ public class ExportCSVTask extends AsyncTask { try { - File dir = DatabaseHelper.getFilesDir(activity, "CSV"); + File dir = DatabaseHelper.getFilesDir("CSV"); if(dir == null) return null; HabitsCSVExporter exporter = new HabitsCSVExporter(selectedHabits, dir); diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java index f5f70809f..abc31b490 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java @@ -19,14 +19,11 @@ package org.isoron.uhabits.tasks; -import android.content.Intent; -import android.net.Uri; import android.os.AsyncTask; +import android.support.annotation.Nullable; import android.view.View; import android.widget.ProgressBar; -import org.isoron.uhabits.R; -import org.isoron.uhabits.ReplayableActivity; import org.isoron.uhabits.helpers.DatabaseHelper; import java.io.File; @@ -34,14 +31,23 @@ import java.io.IOException; public class ExportDBTask extends AsyncTask { - private final ReplayableActivity activity; + public interface Listener + { + void onExportDBFinished(@Nullable String filename); + } + private ProgressBar progressBar; private String filename; + private Listener listener; - public ExportDBTask(ReplayableActivity activity, ProgressBar progressBar) + public ExportDBTask(ProgressBar progressBar) { this.progressBar = progressBar; - this.activity = activity; + } + + public void setListener(Listener listener) + { + this.listener = listener; } @Override @@ -57,19 +63,8 @@ public class ExportDBTask extends AsyncTask @Override protected void onPostExecute(Void aVoid) { - if(filename != null) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/octet-stream"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename))); - - activity.startActivity(intent); - } - else - { - activity.showToast(R.string.could_not_export); - } + if(listener != null) + listener.onExportDBFinished(filename); if(progressBar != null) progressBar.setVisibility(View.GONE); @@ -82,7 +77,7 @@ public class ExportDBTask extends AsyncTask try { - File dir = DatabaseHelper.getFilesDir(activity, "Backups"); + File dir = DatabaseHelper.getFilesDir("Backups"); if(dir == null) return null; filename = DatabaseHelper.saveDatabaseCopy(dir); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43a91f004..394b5dd39 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -139,13 +139,13 @@ Custom … Help & FAQ Failed to export data. - Failed to import habits from file. - File type not recognized. + Failed to import data. + File not recognized. Habits imported successfully. - Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information. + Full backup successfully exported. Import data - Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc, but cannot be imported back. Export full backup - Generates a file that contains all your data, and that can be imported back. - Full backup successfully exported. + Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information. + Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc. This file cannot be imported back. + Generates a file that contains all your data. This file can be imported back. \ No newline at end of file