Refactor and write tests for IO tasks

pull/77/merge
Alinson S. Xavier 10 years ago
parent 3656c51e95
commit 9e410f8395

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

@ -52,17 +52,13 @@ public class ImportTest
{ {
private File baseDir; private File baseDir;
private Context context; private Context context;
private Context targetContext;
@Before @Before
public void setup() public void setup()
{ {
HabitFixtures.purgeHabits(); HabitFixtures.purgeHabits();
context = InstrumentationRegistry.getInstrumentation().getContext(); context = InstrumentationRegistry.getInstrumentation().getContext();
targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); baseDir = DatabaseHelper.getFilesDir("Backups");
baseDir = DatabaseHelper.getFilesDir(targetContext, "Backups");
if(baseDir == null) fail("baseDir should not be null"); if(baseDir == null) fail("baseDir should not be null");
} }

@ -0,0 +1,77 @@
/*
* 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.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<Habit> 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);
}
}

@ -0,0 +1,71 @@
/*
* 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.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);
}
}

@ -0,0 +1,108 @@
/*
* 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.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");
}
}

@ -48,7 +48,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
void onFileSelected(File file); void onFileSelected(File file);
} }
private OnFileSelectedListener fileListener; private OnFileSelectedListener listener;
public FilePickerDialog(Activity activity, File initialDirectory) public FilePickerDialog(Activity activity, File initialDirectory)
{ {
@ -81,7 +81,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
} }
else else
{ {
if (fileListener != null) fileListener.onFileSelected(file); if (listener != null) listener.onFileSelected(file);
dialog.dismiss(); dialog.dismiss();
} }
} }
@ -91,9 +91,9 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
dialog.show(); dialog.show();
} }
public void setFileListener(OnFileSelectedListener fileListener) public void setListener(OnFileSelectedListener listener)
{ {
this.fileListener = fileListener; this.listener = listener;
} }
private void navigateTo(File path) private void navigateTo(File path)

@ -26,6 +26,7 @@ import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
@ -73,7 +74,8 @@ import java.util.List;
public class ListHabitsFragment extends Fragment public class ListHabitsFragment extends Fragment
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener, implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener, OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener,
HabitSelectionCallback.Listener, ImportDataTask.Listener HabitSelectionCallback.Listener, ImportDataTask.Listener, ExportCSVTask.Listener,
ExportDBTask.Listener
{ {
long lastLongClick = 0; long lastLongClick = 0;
private boolean isShortToggleEnabled; private boolean isShortToggleEnabled;
@ -437,7 +439,7 @@ public class ListHabitsFragment extends Fragment
if(dir == null) return; if(dir == null) return;
FilePickerDialog picker = new FilePickerDialog(activity, dir); FilePickerDialog picker = new FilePickerDialog(activity, dir);
picker.setFileListener(new FilePickerDialog.OnFileSelectedListener() picker.setListener(new FilePickerDialog.OnFileSelectedListener()
{ {
@Override @Override
public void onFileSelected(File file) public void onFileSelected(File file)
@ -447,6 +449,7 @@ public class ListHabitsFragment extends Fragment
task.execute(); task.execute();
} }
}); });
picker.show(); picker.show();
} }
@ -472,11 +475,49 @@ public class ListHabitsFragment extends Fragment
public void exportAllHabits() 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() 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);
}
} }
} }

@ -124,8 +124,11 @@ public class DatabaseHelper
} }
@Nullable @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 chosenDir = null;
File externalFilesDirs[] = ContextCompat.getExternalFilesDirs(context, null); File externalFilesDirs[] = ContextCompat.getExternalFilesDirs(context, null);
if(externalFilesDirs == null) return null; if(externalFilesDirs == null) return null;

@ -19,14 +19,11 @@
package org.isoron.uhabits.tasks; package org.isoron.uhabits.tasks;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.support.annotation.Nullable;
import android.view.View; import android.view.View;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ReplayableActivity;
import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.io.HabitsCSVExporter;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
@ -37,17 +34,25 @@ import java.util.List;
public class ExportCSVTask extends AsyncTask<Void, Void, Void> public class ExportCSVTask extends AsyncTask<Void, Void, Void>
{ {
private final ReplayableActivity activity; public interface Listener
{
void onExportCSVFinished(@Nullable String archiveFilename);
}
private ProgressBar progressBar; private ProgressBar progressBar;
private final List<Habit> selectedHabits; private final List<Habit> selectedHabits;
String archiveFilename; private String archiveFilename;
private ExportCSVTask.Listener listener;
public ExportCSVTask(ReplayableActivity activity, List<Habit> selectedHabits, public ExportCSVTask(List<Habit> selectedHabits, ProgressBar progressBar)
ProgressBar progressBar)
{ {
this.selectedHabits = selectedHabits; this.selectedHabits = selectedHabits;
this.progressBar = progressBar; this.progressBar = progressBar;
this.activity = activity; }
public void setListener(Listener listener)
{
this.listener = listener;
} }
@Override @Override
@ -63,19 +68,8 @@ public class ExportCSVTask extends AsyncTask<Void, Void, Void>
@Override @Override
protected void onPostExecute(Void aVoid) protected void onPostExecute(Void aVoid)
{ {
if(archiveFilename != null) if(listener != null)
{ listener.onExportCSVFinished(archiveFilename);
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(progressBar != null) if(progressBar != null)
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
@ -86,7 +80,7 @@ public class ExportCSVTask extends AsyncTask<Void, Void, Void>
{ {
try try
{ {
File dir = DatabaseHelper.getFilesDir(activity, "CSV"); File dir = DatabaseHelper.getFilesDir("CSV");
if(dir == null) return null; if(dir == null) return null;
HabitsCSVExporter exporter = new HabitsCSVExporter(selectedHabits, dir); HabitsCSVExporter exporter = new HabitsCSVExporter(selectedHabits, dir);

@ -19,14 +19,11 @@
package org.isoron.uhabits.tasks; package org.isoron.uhabits.tasks;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.support.annotation.Nullable;
import android.view.View; import android.view.View;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ReplayableActivity;
import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.helpers.DatabaseHelper;
import java.io.File; import java.io.File;
@ -34,14 +31,23 @@ import java.io.IOException;
public class ExportDBTask extends AsyncTask<Void, Void, Void> public class ExportDBTask extends AsyncTask<Void, Void, Void>
{ {
private final ReplayableActivity activity; public interface Listener
{
void onExportDBFinished(@Nullable String filename);
}
private ProgressBar progressBar; private ProgressBar progressBar;
private String filename; private String filename;
private Listener listener;
public ExportDBTask(ReplayableActivity activity, ProgressBar progressBar) public ExportDBTask(ProgressBar progressBar)
{ {
this.progressBar = progressBar; this.progressBar = progressBar;
this.activity = activity; }
public void setListener(Listener listener)
{
this.listener = listener;
} }
@Override @Override
@ -57,19 +63,8 @@ public class ExportDBTask extends AsyncTask<Void, Void, Void>
@Override @Override
protected void onPostExecute(Void aVoid) protected void onPostExecute(Void aVoid)
{ {
if(filename != null) if(listener != null)
{ listener.onExportDBFinished(filename);
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(progressBar != null) if(progressBar != null)
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
@ -82,7 +77,7 @@ public class ExportDBTask extends AsyncTask<Void, Void, Void>
try try
{ {
File dir = DatabaseHelper.getFilesDir(activity, "Backups"); File dir = DatabaseHelper.getFilesDir("Backups");
if(dir == null) return null; if(dir == null) return null;
filename = DatabaseHelper.saveDatabaseCopy(dir); filename = DatabaseHelper.saveDatabaseCopy(dir);

@ -139,13 +139,13 @@
<string name="custom_frequency">Custom …</string> <string name="custom_frequency">Custom …</string>
<string name="help">Help &amp; FAQ</string> <string name="help">Help &amp; FAQ</string>
<string name="could_not_export">Failed to export data.</string> <string name="could_not_export">Failed to export data.</string>
<string name="could_not_import">Failed to import habits from file.</string> <string name="could_not_import">Failed to import data.</string>
<string name="file_not_recognized">File type not recognized.</string> <string name="file_not_recognized">File not recognized.</string>
<string name="habits_imported">Habits imported successfully.</string> <string name="habits_imported">Habits imported successfully.</string>
<string name="import_data_summary">Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information.</string> <string name="full_backup_success">Full backup successfully exported.</string>
<string name="import_data">Import data</string> <string name="import_data">Import data</string>
<string name="export_as_csv_summary">Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc, but cannot be imported back.</string>
<string name="export_full_backup">Export full backup</string> <string name="export_full_backup">Export full backup</string>
<string name="export_full_backup_summary">Generates a file that contains all your data, and that can be imported back.</string> <string name="import_data_summary">Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information.</string>
<string name="full_backup_success">Full backup successfully exported.</string> <string name="export_as_csv_summary">Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc. This file cannot be imported back.</string>
<string name="export_full_backup_summary">Generates a file that contains all your data. This file can be imported back.</string>
</resources> </resources>
Loading…
Cancel
Save