Compare commits

...

268 Commits

Author SHA1 Message Date
692fe3218f Merge branch 'release/1.7.0' 2017-04-09 18:13:52 -04:00
387930c08d Fix icon size, remove unused icons 2017-04-09 17:37:14 -04:00
6bd31f9607 Add Basque, Catalan, Persian and Romanian languages 2017-04-09 17:16:08 -04:00
9aafe7160c Update translation links 2017-04-09 16:56:04 -04:00
5cc4aac67a Update translations 2017-04-09 16:41:32 -04:00
831421bc98 Ignore CrowdIn configuration files 2017-04-09 16:03:00 -04:00
161d8f2517 Update changelog 2017-03-31 09:12:53 -04:00
bfe4b822b3 Bump version 2017-03-31 08:15:04 -04:00
19e79a8559 Fix failing test 2017-03-20 22:32:53 -04:00
876d4f0ac7 Merge branch 'feature/scroll-header' into dev
Fixes #155
2017-03-20 20:05:26 -04:00
f4f7faf3a4 Fix a few issues with header scrolling 2017-03-20 20:00:10 -04:00
56f2ae57fe Persist scrolling after configuration change 2017-03-20 18:53:48 -04:00
3fe09efe9b Scroll checkmarks 2017-03-19 20:36:16 -04:00
f0de29fbfe Make HeaderView scrollable 2017-03-19 18:31:43 -04:00
324facfffd Re-enable haptic feedback on CheckmarkButtonView 2017-03-19 16:35:57 -04:00
03e58f9ef2 Preserve position of ScrollableChart on configuration changes
Fixes #240
2017-03-18 23:57:54 -04:00
e6c9f7f0c9 Preserve ScrollView position after configuration change
Fixes #239
2017-03-18 22:41:32 -04:00
42bdedb86a Update dependencies 2017-03-18 22:40:38 -04:00
ab0c510fda Rename export action and move to overflow menu 2017-03-18 22:22:07 -04:00
e46fd58664 Fix ImportTest on Nougat 2017-03-17 22:49:52 -04:00
8532bd402e Update APK filename 2017-03-17 22:11:48 -04:00
2c599b18ef Fix ambiguous reference to R 2017-03-17 21:52:51 -04:00
0d78ba4ba9 Update gradle 2017-03-17 21:52:13 -04:00
Janet Do
611dfa00a5 Export data from the statistics screen
Closes #27
2017-02-02 21:31:37 -05:00
Anirudha Agashe
54a195243d Fixed failing build
Replaced static method to obtain context with appropriate dependency
2017-01-18 12:58:56 -06:00
Anirudha Agashe
4fc30fae53 Remove use of static context from HabitsApplication class (#224)
Remove static context from application class. Replace static method to obtain context with appropriate dependency. Remove context from controller. Add auto factory to export db task.
2017-01-09 23:09:05 -05:00
b3fe9c65d2 Refresh data at midnight
Fixes #221
2016-12-31 18:24:26 -05:00
09f1ae8765 Update circle.yml 2016-12-31 12:30:10 -05:00
0a8b763ece Make TimePicker slightly smaller
Fixes #219
2016-12-30 12:58:37 -05:00
edd5f25529 Update circle.yml 2016-12-25 23:56:34 -05:00
d81fdb41dc Remove temp file after importing 2016-12-25 23:50:38 -05:00
02c8810e46 Bump targetSdkVersion to 25 2016-12-25 23:05:47 -05:00
6adf8061d3 Use FileProvider instead of File URIs 2016-12-25 23:05:17 -05:00
d19d57e5df Use Storage Access Framework when importing files 2016-12-25 11:54:08 -05:00
fd82e6c24b Remember last used order 2016-11-24 06:00:02 -05:00
56263efa39 Sort by score 2016-11-24 05:45:08 -05:00
d5eacba303 Disable drag-and-drop when automatically sorting 2016-11-24 05:27:26 -05:00
222261c674 Add order menu 2016-11-23 06:35:07 -05:00
b1a06df7f8 Implement automatic sorting for SQLHabitList 2016-11-23 05:58:46 -05:00
a1fc7dd0d1 Implement automatic sorting for MemoryHabitList 2016-11-04 05:59:34 -04:00
Justin Inácio
10131d5124 Allow intervals larger than 99 days (#202) 2016-11-02 06:13:31 -04:00
aa94959ad2 Merge tag 'v1.6.2' into dev
v1.6.2
2016-10-13 07:09:19 -04:00
967dc2586b Merge branch 'hotfix/1.6.2' 2016-10-13 07:09:11 -04:00
908fd1d6ae Fix NoSuchMethodError on Android 4.1 2016-10-11 06:34:22 -04:00
45fd8a29e1 Merge tag 'v1.6.1' into dev
v1.6.1
2016-10-10 12:10:23 -04:00
4624acd477 Merge branch 'hotfix/1.6.1' 2016-10-10 12:10:14 -04:00
c8cd4fa389 Delete old database cache 2016-10-10 11:39:18 -04:00
8c4fab28aa Merge tag 'v1.6.0' into dev
v1.6.0
2016-10-10 09:54:29 -04:00
d6b91cef01 Merge branch 'release/1.6.0' 2016-10-10 09:54:20 -04:00
e273fe7375 Automatically repair inconsistent databases before crashing 2016-10-10 08:49:16 -04:00
ab3b946c65 Update translations 2016-10-09 18:23:24 -04:00
38cd47c199 Remove empty title bar on TimePickerDialog 2016-10-09 13:18:36 -04:00
fa64bd389d Update list of developers 2016-10-09 12:47:33 -04:00
d03578fd9e Update changelog 2016-10-09 12:40:02 -04:00
79ae181df2 Bump version to 1.6.0 2016-10-09 12:14:03 -04:00
320dca9070 Automatically change order of days on pre-lollipop 2016-10-09 12:05:57 -04:00
f3b9fc825a HabitMatcher: only compute today's value if needed 2016-10-09 11:58:03 -04:00
50da14022f Fix bug that prevented ringtone from being saved 2016-10-09 11:47:30 -04:00
84a02fe541 Remove duplicate shadows and some duplicate code 2016-10-09 11:32:50 -04:00
b3aa3d14c0 Remove unused resources 2016-10-04 07:52:36 -04:00
e9cdfd23c4 Fix widgets on API 15 2016-10-04 07:43:40 -04:00
659ad2d817 Fix card background color (pre-lollipop) 2016-10-04 07:37:40 -04:00
d537ba0dfa Fix layout issues on pre-lollipop 2016-10-04 07:29:51 -04:00
7c14725d88 Always show menu icons 2016-10-04 06:57:01 -04:00
638b3f763c Fix some crashes on old devices 2016-09-30 08:10:25 -04:00
f876fc50bb Only recalculate all list positions when repairing database 2016-09-30 07:24:29 -04:00
919504ccfb Render widgets in a separate thread 2016-09-30 07:03:23 -04:00
1a8d9e72a1 Add Tasker screenshots 2016-09-25 11:55:16 -04:00
8e82f369c7 Implement Tasker/Locale plugin 2016-09-25 11:01:17 -04:00
0a5677211e Remove reference to MainActivity 2016-09-24 17:02:30 -04:00
65071797c9 SettingsActivity: Fix toolbar color in night mode 2016-09-24 16:49:45 -04:00
df1751b21a Refactor AboutActivity 2016-09-24 16:41:14 -04:00
4e952dd87a Remove MainActivity and use activity-alias 2016-09-24 16:03:41 -04:00
Nikhil
132dce8919 Improve error message on widget
Fixes #168
2016-09-19 21:24:32 -04:00
5c8450191a Make notifications actionable on the Pebble
Closes #126
2016-09-19 18:32:03 -04:00
b1d00598eb Add option to repair database 2016-09-18 08:09:18 -04:00
518ade3165 Add some database checks 2016-09-18 08:08:56 -04:00
dc5d7930a6 Merge pull request #173 from JotraN/dev
Add exporting scores and checkmarks files with multiple habits (#68).
2016-09-17 12:38:27 -04:00
jotran
ec34043041 Add exporting scores and checkmarks files with multiple habits (#68).
Add writeMultipleHabits to write a scores file and a checkmarks file
that contains scores and checkmarks of multiple habits.
Add getTimeframe because it was necessary to get the timeframe between
all habits so that row data could be populated correctly for habits that
started before/after other habits.
Move writeCheckmarks to below writeScores since it wasn't called until
after writeScores in writeHabits.

Add getByInterval to ScoreList, MemoryScoreList, and SQLiteScoreList to
get scores between a given interval - simiarly to CheckmarkList.

Add getValues (ScoreList) to get all values between a given timeframe.

Add getNewest to get the newest repetition in a list and to correspond
with getOldest.

Add getDaysBetween to DateUtils to quickly get the number of days
between two timestamps.

Add tests for the new functions.
2016-09-17 09:04:49 -07:00
4d5407a5cc Fix bug on compute methods that prevented them from recomputing old values 2016-09-16 13:17:52 -04:00
904489d812 Remove debug log 2016-09-16 11:41:20 -04:00
d2436a3165 Merge pull request #174 from Donaira/dev
Increase width of name column according to screen size
2016-09-14 15:35:21 -04:00
Donaira Tamulynaitė
bc19858bca Habit label takes up to 1/3 screen width. 2016-09-14 13:03:38 +03:00
a998a62cdb Remove notification when habit is deleted 2016-09-11 11:35:32 -04:00
1a89bb02be Add option to make notifications sticky 2016-09-11 10:40:43 -04:00
6e8ea471aa Do not cancel notifications automatically 2016-09-11 09:55:47 -04:00
ec42fb54f4 Do not store position of ViewHolder 2016-09-11 09:35:53 -04:00
c46fa84135 Fix bug that made it impossible to disable a reminder 2016-09-11 09:18:06 -04:00
16dcc0cbc2 Fix tests 2016-09-11 09:04:39 -04:00
6f10039aba Merge pull request #171 from regularcoder/dev
Add repetition count to overview card
2016-09-11 08:10:34 -04:00
RegularCoder
5cbc0a3292 Add repetition count to overview card, for #115 2016-09-05 21:14:12 -04:00
7f67a9eb63 Revert concurrent AsyncTasks 2016-09-03 09:37:16 -04:00
d3f7ebd60c SQLiteHabitList: fix corrupted order automatically 2016-09-03 09:33:09 -04:00
461fe1f0b6 Improve performance 2016-09-03 09:25:23 -04:00
8eb9f398d5 Allow multiple AsyncTasks to run concurrently
Fixes #134
2016-09-03 09:00:20 -04:00
f178bcbdd2 CheckmarkButtonView: avoid layout inflation 2016-09-03 08:00:36 -04:00
66c3136fad Allow views to be rendered by the layout editor 2016-09-03 07:46:41 -04:00
4b04966617 Update AMOLED mode upon exiting settings
Fixes #118
2016-09-02 22:24:21 -04:00
3ea21fe823 Update order of checkmark buttons automatically upon exiting settings 2016-09-02 21:13:56 -04:00
e0df69beb6 Prevent progress bar from flickering 2016-09-02 21:03:09 -04:00
b5cd4584b2 Disallow empty WeekdayLists 2016-09-02 21:02:38 -04:00
af7c8b1f2e Merge pull request #170 from regularcoder/dev
Fix issue#65 by setting launchMode to singleTop
2016-09-02 06:30:48 -04:00
RegularCoder
283cb2a3f0 Fix issue#65 by setting launchMode to singleTop 2016-09-01 21:43:28 -04:00
79ddbca307 Fix ripple when days are reversed 2016-08-29 20:57:43 -04:00
46a0777195 Restore progress bars 2016-08-29 20:53:48 -04:00
b35305e16c Preferences: update link to AboutActivity 2016-08-29 20:03:25 -04:00
8c4a745ecb Merge pull request #165 from klanmiko/enhancement/archive
Fix #161 reminder disable on archive
2016-08-22 22:38:03 -04:00
klanmiko
e5162c48ab Scheduler should ignore archived habits
Also consider changing "archive" to "inactive"
2016-08-22 19:00:36 -07:00
b54c4de5f7 ShowHabitRootView: update title automatically
Fixes #162
2016-08-22 17:02:20 -04:00
2bdc0b4f5e Update unit tests for ListHabitsMenu 2016-08-22 16:48:58 -04:00
22db61db01 Fix crash on startup and after dismissing notification 2016-08-22 16:40:23 -04:00
c7cfbd1643 Allow cards to be rendered by the layout editor 2016-08-07 08:43:18 -04:00
8655437f3e Change 'show completed' to 'hide completed' 2016-08-05 19:37:47 -04:00
3ad3cf54ec Add tests for HabitCardView 2016-08-05 07:54:06 -04:00
9a470cc61d Initialize db earlier; fixes crash 2016-08-05 06:30:32 -04:00
fc2087fe68 Write tests for ListHabits controller, menu and screen 2016-08-04 21:56:38 -04:00
7b8ab6a625 Dismiss notification automatically 2016-08-04 07:46:10 -04:00
2d40fb0b82 Write tests for receivers 2016-08-04 07:34:38 -04:00
c961045b63 Call refresh from UI thread 2016-08-04 06:15:43 -04:00
d0db3359fe Allow StyledResources to receive a fixed theme for testing 2016-08-03 22:27:49 -04:00
11378e07bf Properly initialize appComponent 2016-08-03 22:13:30 -04:00
8132188e46 Replace Singleton by AppScope 2016-08-03 07:48:58 -04:00
7eb454788f Refactor receivers 2016-08-03 07:37:00 -04:00
c262adbe85 Move Preferences to their own package 2016-07-31 08:47:20 -04:00
05aa5b1172 Replace InterfaceUtils theme methods by ThemeSwitcher 2016-07-31 08:42:40 -04:00
dd3d78b82c Filter Dagger classes from coverage report 2016-07-31 08:39:47 -04:00
3d4ae2126b Remove DialogFactory 2016-07-31 08:03:07 -04:00
5aa9114aff Fix name of Ukrainian language 2016-07-30 21:58:06 -04:00
77e0ad007f Update list of developers 2016-07-30 21:56:27 -04:00
2e53b75705 Merge pull request #151 from sciamano/freq2
relative size of the marks in the frequency view
2016-07-30 21:24:30 -04:00
Denis
0ec03035f5 maximal frequency of the data 2016-07-30 21:26:09 +02:00
Denis
67ae48b527 radius and color of the mark in the frequency view 2016-07-30 21:26:06 +02:00
Denis
82d2931559 fix code duplication 2016-07-30 21:26:01 +02:00
a527140802 Use the same HabitCardListCache for all activities
Fixes #147
2016-07-29 07:40:56 -04:00
9ec3d9048a Make Preferences listen for external changes 2016-07-29 07:28:44 -04:00
3ed4f3b280 Make scrolling very smooth again 2016-07-29 07:21:47 -04:00
f101975320 Take timezone into account when scheduling alarms 2016-07-29 07:07:02 -04:00
3a7f27755c Use hashmap on AndroidTaskRunner 2016-07-29 07:06:49 -04:00
3a3be664f7 Fix some bugs on fragments and model listeners 2016-07-28 21:45:54 -04:00
33ae8d1edf Show empty message at startup 2016-07-28 21:45:54 -04:00
cb3b8f2cde Reorganize sections and add GPLv3 logo 2016-07-28 18:30:11 -04:00
307e025b1a Fix CheckmarkListTest 2016-07-28 08:00:01 -04:00
03dd24d17b Clean up BaseUnitTest 2016-07-28 07:42:03 -04:00
5f4ac21a41 Small changes 2016-07-27 20:29:13 -04:00
b33616d363 Remove old .gitmodules 2016-07-27 19:30:37 -04:00
b7d1c6d254 Update copyright notice 2016-07-27 19:01:05 -04:00
bd30ceee1b Fix small layout issue. 2016-07-27 18:58:51 -04:00
fe96313162 Update NOTICE.md 2016-07-27 18:56:24 -04:00
938fe3325e Remove disabled test 2016-07-27 18:13:02 -04:00
237de035bb Remove remaining static references to app component 2016-07-27 18:09:36 -04:00
ddc85ced0b Remove waitForTasks 2016-07-27 17:10:07 -04:00
eceb1bfb7d Remove most static references to app component; fix tests 2016-07-27 17:05:51 -04:00
3b737996e9 Reorganize packages 2016-07-27 00:03:59 -04:00
7f75f9b6da Construct ListHabits using dagger 2016-07-26 23:52:39 -04:00
74f78f0fdf Improve usage of dagger 2016-07-26 19:26:42 -04:00
ef63dd19e7 Fix reference to MainActivity 2016-07-25 17:03:09 -04:00
c7878d979f Switch from toasts to snackbars 2016-07-25 08:58:28 -04:00
ebd294be63 Split InterfaceUtils 2016-07-25 08:31:26 -04:00
748cec06a8 Dagger: replace injects by getters 2016-07-23 21:18:20 -04:00
15a4a2c002 Refactor reminders; replace int by WeekdayList 2016-07-23 19:19:22 -04:00
37a9e793e7 Add tests for HabitCardListCache; refactor TaskRunners 2016-07-23 18:06:03 -04:00
d54de9df89 Refactor tasks; break progress bars 2016-07-23 11:45:59 -04:00
94a5db2208 Add view tests 2016-07-22 08:01:44 -04:00
a984467516 Some refactoring; add tests for ListHabitsScreen 2016-07-21 22:48:37 -04:00
d6dacfd24b Refactor reminder scheduling, add tests 2016-07-20 17:01:24 -04:00
3938ae6fa8 Fix test sizes 2016-07-18 20:57:21 -04:00
7dbbc51a9a Add task for combined coverage report 2016-07-18 10:53:23 -04:00
c9d1bb821c Refactor pending intents 2016-07-18 09:07:21 -04:00
77f406dcee Refactor broadcast receivers and add a public receiver
Fixes #6
2016-07-17 17:28:14 -04:00
61b0b1fdea Use separate broadcast actions for widgets and notifications 2016-07-17 09:32:42 -04:00
8cde0d6aca HistoryChart: make toggling faster 2016-07-17 09:32:21 -04:00
fa9f90a09e Refactor HeaderView; update list on resume 2016-07-17 08:59:34 -04:00
28eb615b0e Notification: add checkmark instead of toggling 2016-07-17 08:43:14 -04:00
c1e10e09a5 Refresh list after importing DB 2016-07-17 08:30:07 -04:00
35e93fddc6 Use dynamic number of streaks on widget 2016-07-17 08:28:19 -04:00
33596a2797 Restore dynamic number of checkmarks 2016-07-17 08:02:31 -04:00
207f026ceb Fix habit creation 2016-07-16 10:35:55 -04:00
0f12d02990 Use same cache for every SQLHabitList 2016-07-16 10:12:06 -04:00
bc4bbaefac Remove some drag-and-drop glitches 2016-07-15 09:33:57 -04:00
a802053ef7 Update .gitignore 2016-07-15 09:33:07 -04:00
cf98d1a5c3 AndroidTest: Increase timeout 2016-07-14 11:54:15 -04:00
cc1e56894b Fix test scripts 2016-07-14 11:30:21 -04:00
06b5f89b7a Make habit deletions instantaneous
Fixes #133
2016-07-14 10:57:45 -04:00
17423b3ecd Fix most issues with the RecyclerView; improve loading 2016-07-14 10:15:56 -04:00
1526f617c5 Remove most animation glitches on RecyclerView; disable reordering 2016-07-11 22:06:30 -04:00
365eb400d0 Add proguard file 2016-07-11 09:04:36 -04:00
785d57c778 Merge branch 'dev' of github.com:iSoron/uhabits into dev 2016-07-11 09:03:24 -04:00
8e9f1aa166 Add basic API for the Pebble app 2016-07-11 08:47:00 -04:00
b2f97cb0a5 Merge pull request #145 from ajbarnes/dev
Add 24hr snooze. Fixes #103
2016-07-06 04:39:00 -04:00
Adam Barnes
4c1ff4f0a8 Add 24hr snooze. Fixes #103 2016-07-05 23:44:16 -04:00
94a48133ec Merge branch 'feature/recyclerview' into dev 2016-07-01 10:22:06 -04:00
71dd6cde89 Restore drag & drop and click; remove DragSortListView library 2016-07-01 09:23:07 -04:00
b33420cabb Replace ListView with RecyclerView 2016-07-01 08:03:30 -04:00
dbe268b8e9 Add simple filter to menu 2016-06-30 07:23:28 -04:00
3d53812d7f Restore CircleCI badge 2016-06-29 21:31:34 -04:00
5b4b436f0f CircleCI: use JDK8 2016-06-29 21:10:22 -04:00
6ef2983906 Implement filter by colors 2016-06-29 20:12:09 -04:00
b749f7f391 Fix CircleCI scripts 2016-06-29 20:11:39 -04:00
922b234307 Initial implementation of filters 2016-06-29 19:49:19 -04:00
31fdae1c8b Merge branch 'feature/refactoring-mvc' into dev 2016-06-29 12:23:04 -04:00
5d61fdd3d0 Write tests for HintView 2016-06-29 10:03:56 -04:00
588b45d47b Merge branch 'dev' into feature/refactoring-mvc 2016-06-28 22:48:45 -04:00
9a059275ce Merge branch 'dev' into feature/refactoring-mvc 2016-06-28 22:46:17 -04:00
4ae813e6f9 Restore all widgets 2016-06-28 22:39:24 -04:00
52701666bc Start refactoring widgets 2016-06-28 12:11:34 -04:00
a90e26691f Fix HistoryEditorDialog 2016-06-26 17:10:17 -04:00
f0c62a5908 Update run_tests script to run JVM tests 2016-06-26 17:07:38 -04:00
e899a70eb0 Fix most tests 2016-06-25 05:53:19 -04:00
aa41717c66 Remove CircleCI temporarily 2016-06-25 05:43:49 -04:00
6ba6d7c8c1 Refactor ShowHabit screen; remove fragment 2016-06-24 14:52:41 -04:00
efd0d1e051 Refactor ShowHabit fragment; break widgets 2016-06-24 08:39:48 -04:00
a11ad6e909 ShowHabit: Refactor score chart 2016-06-22 19:47:16 -04:00
a060cbe578 ShowHabit: Refactor subtitle and overview 2016-06-22 08:48:08 -04:00
a445ca962b Merge tag 'v1.5.6' into dev
v1.5.6
2016-06-19 17:10:17 -04:00
2b6fc06b86 Merge branch 'hotfix/1.5.6' 2016-06-19 17:10:12 -04:00
98ad3aab9d Make checkmark widgets work again 2016-06-19 17:06:03 -04:00
c7e63a40da Bump version to 1.5.6 2016-06-19 17:04:25 -04:00
b6ed33b1e6 Merge branch 'dev' into feature/refactoring-mvc 2016-06-19 16:16:04 -04:00
91e39372a1 Merge tag 'v1.5.5' into dev
v1.5.5
2016-06-19 10:08:26 -04:00
c2dd26eeb3 Merge branch 'hotfix/1.5.5' 2016-06-19 10:08:19 -04:00
5831340343 Update French translation; add Slovenian and Croatian translations 2016-06-19 09:57:45 -04:00
d7f6f52a49 Always refresh data after resuming
Fixes #120
2016-06-19 09:32:42 -04:00
93b442332d Bugfix: add check on correct date when checking from notification 2016-06-19 09:08:34 -04:00
e248824bcd Bump version to 1.5.5 2016-06-19 08:08:00 -04:00
ec4a381d70 Refactor Habit class 2016-06-17 07:29:48 -04:00
b13f2b4228 Create a class for Reminders 2016-06-16 15:19:02 -04:00
efc7b2cebb Replace ActiveAndroid queries with raw SQLite queries 2016-06-16 13:51:36 -04:00
add08d6054 Fix reordering 2016-06-15 10:12:33 -04:00
5d8a348aaf Hide empty message at startup 2016-06-15 09:54:39 -04:00
ec0e8ac24c Cancel selection with back button correctly 2016-06-15 09:48:01 -04:00
fee3137a6f Move ripple to correct place 2016-06-15 05:39:13 -04:00
abe6b10964 Restore dynamic checkmark button count 2016-06-15 05:32:20 -04:00
6484b96e5a Reformat and reorganize some code 2016-06-15 05:04:12 -04:00
3d3d5b9b96 Fix top navigation on Settings and About screens 2016-06-15 05:02:13 -04:00
14364901ff Reorganize instrumented tests 2016-06-14 05:46:31 -04:00
440706882b Move command tests to JVM 2016-06-14 05:30:30 -04:00
ca9d56e59e Fix toolbars 2016-06-12 18:22:04 -04:00
9a6dafaa79 Improve documentation 2016-06-12 09:23:55 -04:00
9a44774284 Add instrumented unit tests for SQLite lists 2016-06-12 07:55:47 -04:00
2b23b36e36 Move remaining model tests to JVM; simplify SQLite implementation 2016-06-10 18:55:32 -04:00
78d4f86cab Separate ActiveAndroid from models 2016-06-10 13:30:33 -04:00
18e8390aed Merge branch 'dev' into feature/refactoring-mvc 2016-06-05 04:37:46 -04:00
cabcd5b1bf CSV export: allow spaces on filename and fix tests 2016-06-04 22:06:01 -04:00
7e8a2a0c1c Major refactoring of ListHabitsActivity 2016-06-04 18:10:33 -04:00
650971bf36 Merge tag 'v1.5.4' into dev
Version 1.5.4
2016-05-29 12:06:46 -04:00
00774368d4 Merge branch 'hotfix/1.5.4' 2016-05-29 12:06:36 -04:00
cbf1bd3e19 Update changelog 2016-05-29 11:57:33 -04:00
4061921b93 Add Serbian translation 2016-05-29 11:56:57 -04:00
59d42fe62f Show empty ringtone name in case of RuntimeException
Fixes #116
2016-05-29 11:47:36 -04:00
3ffa079e24 Remove code duplication 2016-05-28 13:03:37 -04:00
bb950d61fc Move sendFile and sendEmail to Screen 2016-05-28 12:21:38 -04:00
0ad0e5cf36 Fix time out on tests 2016-05-28 12:20:02 -04:00
3b56d6d596 Add missing file 2016-05-28 10:16:21 -04:00
8d57273987 Refactor EditHabitDialog 2016-05-28 10:15:55 -04:00
b98853ab26 Introduce ButterKnife 2016-05-27 23:02:04 -04:00
071cad73d4 Make models observable, refactor ShowHabitFragment 2016-05-27 22:26:33 -04:00
16dc9c25d2 Fix bug that caused checkmarks not to update sometimes 2016-05-27 14:06:55 -04:00
6b23858136 Create HabitListView and move most code from Fragment to it 2016-05-27 12:19:29 -04:00
e0198e9926 Reorganize files 2016-05-27 10:20:16 -04:00
6445bf62bc Refactor MainActivity and Preferences 2016-05-26 16:35:47 -04:00
83ef8564e1 Reorganize file tree according to feature 2016-05-26 10:38:59 -04:00
88e8aad0d8 CSV export: sanitize habit name before creating folder
Fix #113
2016-05-25 10:09:39 -04:00
a4b6728721 Bump version to 1.5.4 2016-05-25 10:09:11 -04:00
ca9745f550 Merge tag 'v1.5.3' into dev
Version 1.5.3
2016-05-22 12:56:36 -04:00
605593d739 Update UI tests 2016-05-19 12:51:36 -04:00
bfbfcbd8cf Merge tag 'v1.5.2' into dev
Version 1.5.2
2016-05-19 12:01:37 -04:00
dbf569ff87 Merge tag 'v1.5.1' into dev
v1.5.1
2016-05-17 09:36:00 -04:00
2c80544aaa Merge tag 'v1.5.0' into dev
Version 1.5.0
2016-05-15 08:43:39 -04:00
528 changed files with 33479 additions and 15836 deletions

51
.gitignore vendored
View File

@@ -1,36 +1,23 @@
#built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.apk
*.class
# generated files
bin/
gen/
# Local configuration file (sdk path, etc)
local.properties
# Windows thumbnail db
Thumbs.db
# OSX files
.DS_Store
# Eclipse project files
.classpath
.project
# Android Studio
.idea
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
.gradle
build/
*.dex
*.iml
*.local.*
*.swp
*.trace
*~
.DS_Store
.classpath
.gradle
.idea
.project
Thumbs.db
art/
*.actual.png
bin/
build/
captures/
docs/
gen/
local.properties
crowdin.yaml

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "libs/drag-sort-listview"]
path = libs/drag-sort-listview
url = https://github.com/iSoron/drag-sort-listview.git

View File

@@ -1,5 +1,51 @@
# Changelog
### 1.7.0 (Mar 31, 2017)
* Sort habits automatically
* Allow swiping the header to see previous days
* Import backups directly from Google Drive or Dropbox
* Refresh data automatically at midnight
* Other minor bug fixes and enhancements
### 1.6.2 (Oct 13, 2016)
* Fix crash on Android 4.1
### 1.6.1 (Oct 10, 2016)
* Fix a crash at startup when database is corrupted
### 1.6.0 (Oct 10, 2016)
* Add option to make notifications sticky
* Add option to hide completed habits
* Display total number of repetitions for each habit
* Pebble integration: check/snooze habits from the watch
* Tasker/Locale integration: allow third-party apps to add checkmarks
* Export an unified CSV file, with checkmarks for all the habits
* Increase width of name column according to screen size
* Stop showing reminders for archived habits
* Add Danish, Dutch, Greek, Hindi and Portuguese (PT) translations
* Other minor fixes and enhancements
### 1.5.6 (Jun 19, 2016)
* Fix bug that prevented checkmark widget from working
### 1.5.5 (Jun 19, 2016)
* Fix bug that prevented check button on notification to work sometimes
* Fix bug that caused back button to apparently erase some checkmarks
* Complete French translation
* Add Croatian and Slovenian translations
### 1.5.4 (May 29, 2016)
* Fix crash upon opening settings screen in some phones
* Fix missing folders in CSV archive
* Add Serbian translation
### 1.5.3 (May 22, 2016)
* Complete Arabic and Czech translations

143
NOTICE.md
View File

@@ -59,28 +59,6 @@ under the SIL OFL 1.1.
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
### DragSortListView
<https://github.com/bauerca/drag-sort-listview>
A subclass of the Android ListView component that enables drag
and drop re-ordering of list items.
Copyright 2012 Carl Bauer
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
### Material Design Icons
<https://github.com/google/material-design-icons>
@@ -107,4 +85,123 @@ Extended linear layout that wrap its content when there is no place in the curre
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
under the License.
### Dagger 2
<https://github.com/google/dagger>
A fast dependency injector for Android and Java.
Copyright 2012 Square, Inc.
Copyright 2012 Google, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
### AutoFactory
<https://github.com/google/auto/tree/master/factory>
A source code generator for JSR-330-compatible factories.
Copyright 2013 Google, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
### Retrolambda
<https://github.com/orfjackal/retrolambda>
Backport of Java 8's lambda expressions to Java 7, 6 and 5
Copyright (c) 2013-2016 Esko Luontola and other Retrolambda contributors
This software is released under the Apache License 2.0.
The license text is at http://www.apache.org/licenses/LICENSE-2.0
### PebbleKit SDK
<https://github.com/pebble/pebble-android-sdk/>
Android PebbleKit SDK to talk to the Pebble via Bluetooth
The MIT License (MIT)
Copyright (c) 2014 - 2015 Pebble Technology
### AppIntro
<https://github.com/PaoloRotolo/AppIntro>
Make a cool intro for your Android app.
Copyright 2015 Paolo Rotolo
Copyright 2016 Maximilian Narr
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
### ButterKnife
<https://github.com/JakeWharton/butterknife>
Bind Android views and callbacks to fields and methods
Copyright 2013 Jake Wharton
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
### opencsv
<http://opencsv.sourceforge.net/>
Opencsv is a very simple csv (comma-separated values) parser library for Java.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,10 +1,13 @@
# Loop Habit Tracker
<a href="https://circleci.com/gh/iSoron/uhabits/tree/dev">
<img src="https://img.shields.io/circleci/project/iSoron/uhabits/dev.svg">
</a>
<!--
<a href="https://codecov.io/github/iSoron/uhabits?branch=dev">
<img src="https://img.shields.io/codecov/c/github/iSoron/uhabits.svg" alt="Coverage via Codecov" />
</a>
-->
Loop is a simple Android app that helps you create and maintain good habits,
allowing you to achieve your long-term goals. Detailed graphs and statistics
@@ -16,6 +19,15 @@ source.
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Git if on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
</p>
## Screenshots
[![Main screen][screen1th]][screen1]
[![Edit habit][screen2th]][screen2]
[![Habit strength][screen3th]][screen3]
[![Habit history and streaks][screen4th]][screen4]
[![Widgets][screen5th]][screen5]
[![Night mode][screen6th]][screen6]
## Features
* **Simple, beautiful and modern interface.** Loop has a minimalistic interface
@@ -47,21 +59,12 @@ source.
and there will never be. The complete source code is available under the
GPLv3.
## Screenshots
[![Main screen][screen1th]][screen1]
[![Edit habit][screen2th]][screen2]
[![Habit strength][screen3th]][screen3]
[![Habit history and streaks][screen4th]][screen4]
[![Widgets][screen5th]][screen5]
[![Night mode][screen6th]][screen6]
## Installing
The easiest way to install Loop is through the [Google Play Store][playstore] or [F-Droid][fdroid].
You may also download and install the APK from the [releases page][releases];
note, however, that the app will not be updated automatically. To build this
app from the source code, see [building instructions][build].
app from the source code, see [build instructions][build].
## Contributing
@@ -89,18 +92,22 @@ contribute, even if you are not a software developer.
## License
This program 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.
<img align="right" src="https://www.gnu.org/graphics/gplv3-88x31.png">
This program 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.
Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
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/>.
[screen1]: screenshots/original/uhabits1.png
[screen2]: screenshots/original/uhabits2.png

View File

@@ -1,19 +1,22 @@
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'jacoco'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "org.isoron.uhabits"
minSdkVersion 15
targetSdkVersion 23
targetSdkVersion 25
buildConfigField "Integer", "databaseVersion", "14"
buildConfigField "Integer", "databaseVersion", "15"
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//testInstrumentationRunnerArgument "size", "small"
testInstrumentationRunnerArgument "size", "medium"
}
buildTypes {
@@ -29,23 +32,59 @@ android {
lintOptions {
checkReleaseBuilds false
}
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
testOptions {
unitTests.all {
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen { false }
showStandardStreams = true
}
}
}
}
dependencies {
compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:design:23.3.0'
compile 'com.android.support:preference-v14:23.3.0'
compile 'com.github.paolorotolo:appintro:3.4.0'
compile 'org.apmem.tools:layouts:1.10@aar'
compile 'com.opencsv:opencsv:3.7'
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
compile project(':libs:drag-sort-listview:library')
androidTestApt 'com.google.dagger:dagger-compiler:2.2'
androidTestCompile 'com.android.support:support-annotations:23.3.0'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support:support-annotations:25.3.0'
androidTestCompile 'com.android.support.test:rules:0.5'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.google.auto.factory:auto-factory:1.0-beta3'
androidTestCompile "com.google.dexmaker:dexmaker:1.2"
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
androidTestCompile 'org.mockito:mockito-core:1.10.19'
apt 'com.google.dagger:dagger-compiler:2.2'
apt 'com.jakewharton:butterknife-compiler:8.0.1'
compile 'com.android.support:appcompat-v7:25.3.0'
compile 'com.android.support:design:25.3.0'
compile 'com.android.support:preference-v14:25.3.0'
compile 'com.android.support:support-v4:25.3.0'
compile 'com.getpebble:pebblekit:3.0.0'
compile 'com.github.paolorotolo:appintro:3.4.0'
compile 'com.google.auto.factory:auto-factory:1.0-beta3'
compile 'com.google.dagger:dagger:2.2'
compile 'com.jakewharton:butterknife:8.0.1'
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
compile 'com.opencsv:opencsv:3.7'
compile 'org.apmem.tools:layouts:1.10@aar'
compile 'org.jetbrains:annotations-java5:15.0'
provided 'javax.annotation:jsr250-api:1.0'
testApt 'com.google.dagger:dagger-compiler:2.2'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:1.10.19'
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.1') {
exclude group: 'com.android.support'
@@ -60,13 +99,44 @@ dependencies {
}
}
task grantAnimationPermission(type: Exec, dependsOn: 'installDebug') {
commandLine "adb shell pm grant org.isoron.uhabits android.permission.SET_ANIMATION_SCALE".split(' ')
retrolambda {
defaultMethods true
}
tasks.whenTaskAdded { task ->
if (task.name.startsWith('connected')) {
task.dependsOn grantAnimationPermission
jacoco {
toolVersion = "0.7.4.201502262128"
}
task coverageReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
jacocoClasspath = configurations['androidJacocoAnt']
reports {
html.enabled = true
}
}
def excludes = [
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*',
'**/*Test*.*',
'**/*$Lambda$*',
'**/*$ViewBinder*',
'**/*MembersInjector*',
'**/*_Provide*',
'**/com/android/**/*',
'android/**/*',
'**/*Dagger*',
'**/*_Factory*'
]
def srcDir = "${project.projectDir}/src/main/java"
def classDir = "${buildDir}/intermediates/classes/debug"
def jvmExecData = "${buildDir}/jacoco/testDebugUnitTest.exec"
def connectedExecData = "${buildDir}/outputs/code-coverage/connected/coverage.ec"
sourceDirectories = files(srcDir)
classDirectories = files(fileTree(dir: classDir, excludes: excludes))
executionData = files(jvmExecData, connectedExecData)
}

3
app/proguard-rules.txt Normal file
View File

@@ -0,0 +1,3 @@
-dontwarn java.beans.**
-dontwarn java.lang.**
-dontobfuscate

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,36 @@
/*
* 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;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.tasks.*;
import dagger.*;
@AppScope
@Component(modules = {
AppModule.class, SingleThreadTaskRunner.class, SQLModelFactory.class
})
public interface AndroidTestComponent extends AppComponent
{
}

View File

@@ -0,0 +1,135 @@
/*
* 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;
import android.appwidget.*;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.support.test.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import java.util.*;
import java.util.concurrent.*;
import static junit.framework.Assert.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class BaseAndroidTest
{
// 8:00am, January 25th, 2015 (UTC)
public static final long FIXED_LOCAL_TIME = 1422172800000L;
private static boolean isLooperPrepared;
protected Context testContext;
protected Context targetContext;
protected Preferences prefs;
protected HabitList habitList;
protected TaskRunner taskRunner;
protected HabitLogger logger;
protected HabitFixtures fixtures;
protected CountDownLatch latch;
protected AndroidTestComponent component;
protected ModelFactory modelFactory;
@Before
public void setUp()
{
if (!isLooperPrepared)
{
Looper.prepare();
isLooperPrepared = true;
}
targetContext = InstrumentationRegistry.getTargetContext();
testContext = InstrumentationRegistry.getContext();
DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME);
setTheme(R.style.AppBaseTheme);
component = DaggerAndroidTestComponent
.builder()
.appModule(new AppModule(targetContext.getApplicationContext()))
.build();
HabitsApplication.setComponent(component);
prefs = component.getPreferences();
habitList = component.getHabitList();
taskRunner = component.getTaskRunner();
logger = component.getHabitsLogger();
modelFactory = component.getModelFactory();
fixtures = new HabitFixtures(modelFactory, habitList);
latch = new CountDownLatch(1);
}
protected void assertWidgetProviderIsInstalled(Class componentClass)
{
ComponentName provider =
new ComponentName(targetContext, componentClass);
AppWidgetManager manager = AppWidgetManager.getInstance(targetContext);
List<ComponentName> installedProviders = new LinkedList<>();
for (AppWidgetProviderInfo info : manager.getInstalledProviders())
installedProviders.add(info.provider);
assertThat(installedProviders, hasItems(provider));
}
protected void awaitLatch() throws InterruptedException
{
assertTrue(latch.await(60, TimeUnit.SECONDS));
}
protected void setTheme(@StyleRes int themeId)
{
targetContext.setTheme(themeId);
StyledResources.setFixedTheme(themeId);
}
protected void sleep(int time)
{
try
{
Thread.sleep(time);
}
catch (InterruptedException e)
{
fail();
}
}
}

View File

@@ -1,68 +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;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.tasks.BaseTask;
import org.junit.Before;
import java.util.concurrent.TimeoutException;
public class BaseTest
{
protected Context testContext;
protected Context targetContext;
private static boolean isLooperPrepared;
public static final long FIXED_LOCAL_TIME = 1422172800000L; // 8:00am, January 25th, 2015 (UTC)
@Before
public void setup()
{
if(!isLooperPrepared)
{
Looper.prepare();
isLooperPrepared = true;
}
targetContext = InstrumentationRegistry.getTargetContext();
testContext = InstrumentationRegistry.getContext();
UIHelper.setFixedTheme(R.style.AppBaseTheme);
DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME);
}
protected void waitForAsyncTasks() throws InterruptedException, TimeoutException
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
{
Thread.sleep(1000);
return;
}
BaseTask.waitForTasks(10000);
}
}

View File

@@ -17,47 +17,46 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.unit.views;
package org.isoron.uhabits;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.SystemClock;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.graphics.*;
import android.os.*;
import android.support.annotation.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.tasks.BaseTask;
import org.isoron.uhabits.views.HabitDataView;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import static junit.framework.Assert.fail;
import static android.view.View.MeasureSpec.*;
import static junit.framework.Assert.*;
public class ViewTest extends BaseTest
public class BaseViewTest extends BaseAndroidTest
{
protected static final double SIMILARITY_CUTOFF = 0.09;
protected static final double DEFAULT_SIMILARITY_CUTOFF = 0.09;
public static final int HISTOGRAM_BIN_SIZE = 8;
protected void measureView(int width, int height, View view)
{
int specWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int specHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
private double similarityCutoff;
view.measure(specWidth, specHeight);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
@Override
public void setUp()
{
super.setUp();
similarityCutoff = DEFAULT_SIMILARITY_CUTOFF;
}
protected void assertRenders(View view, String expectedImagePath) throws IOException
protected void assertRenders(View view, String expectedImagePath)
throws IOException
{
StringBuilder errorMessage = new StringBuilder();
expectedImagePath = getVersionedViewAssetPath(expectedImagePath);
if (view.isLayoutRequested()) measureView(view, view.getMeasuredWidth(),
view.getMeasuredHeight());
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap actual = view.getDrawingCache();
@@ -65,38 +64,140 @@ public class ViewTest extends BaseTest
int width = actual.getWidth();
int height = actual.getHeight();
Bitmap scaledExpected = Bitmap.createScaledBitmap(expected, width, height, true);
Bitmap scaledExpected =
Bitmap.createScaledBitmap(expected, width, height, true);
double distance;
boolean similarEnough = true;
if ((distance = compareHistograms(getHistogram(actual), getHistogram(scaledExpected))) > SIMILARITY_CUTOFF)
if ((distance = compareHistograms(getHistogram(actual),
getHistogram(scaledExpected))) > similarityCutoff)
{
similarEnough = false;
errorMessage.append(String.format(
"Rendered image has wrong histogram (distance=%f). ",
distance));
"Rendered image has wrong histogram (distance=%f). ",
distance));
}
if(!similarEnough)
if (!similarEnough)
{
saveBitmap(expectedImagePath, ".expected", scaledExpected);
String path = saveBitmap(expectedImagePath, "", actual);
errorMessage.append(String.format("Actual rendered image " + "saved to %s", path));
errorMessage.append(
String.format("Actual rendered image saved to %s", path));
fail(errorMessage.toString());
}
actual.recycle();
expected.recycle();
scaledExpected.recycle();
}
@NonNull
protected FrameLayout convertToView(BaseWidget widget,
int width,
int height)
{
widget.setDimensions(
new WidgetDimensions(width, height, width, height));
FrameLayout view = new FrameLayout(targetContext);
RemoteViews remoteViews = widget.getPortraitRemoteViews();
view.addView(remoteViews.apply(targetContext, view));
measureView(view, width, height);
return view;
}
protected int dpToPixels(int dp)
{
return (int) InterfaceUtils.dpToPixels(targetContext, dp);
}
protected void measureView(View view, int width, int height)
{
int specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
view.setLayoutParams(new ViewGroup.LayoutParams(width, height));
view.measure(specWidth, specHeight);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
protected void setSimilarityCutoff(double similarityCutoff)
{
this.similarityCutoff = similarityCutoff;
}
protected void skipAnimation(View view)
{
ViewPropertyAnimator animator = view.animate();
animator.setDuration(0);
animator.start();
}
protected void tap(GestureDetector.OnGestureListener view, int x, int y)
throws InterruptedException
{
long now = SystemClock.uptimeMillis();
MotionEvent e =
MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, dpToPixels(x),
dpToPixels(y), 0);
view.onSingleTapUp(e);
e.recycle();
}
private double compareHistograms(int[][] actualHistogram,
int[][] expectedHistogram)
{
long diff = 0;
long total = 0;
for (int i = 0; i < 256 / HISTOGRAM_BIN_SIZE; i++)
{
diff += Math.abs(actualHistogram[0][i] - expectedHistogram[0][i]);
diff += Math.abs(actualHistogram[1][i] - expectedHistogram[1][i]);
diff += Math.abs(actualHistogram[2][i] - expectedHistogram[2][i]);
diff += Math.abs(actualHistogram[3][i] - expectedHistogram[3][i]);
total += actualHistogram[0][i];
total += actualHistogram[1][i];
total += actualHistogram[2][i];
total += actualHistogram[3][i];
}
return (double) diff / total / 2;
}
private Bitmap getBitmapFromAssets(String path) throws IOException
{
InputStream stream = testContext.getAssets().open(path);
return BitmapFactory.decodeStream(stream);
}
private int[][] getHistogram(Bitmap bitmap)
{
int histogram[][] = new int[4][256 / HISTOGRAM_BIN_SIZE];
for (int x = 0; x < bitmap.getWidth(); x++)
{
for (int y = 0; y < bitmap.getHeight(); y++)
{
int color = bitmap.getPixel(x, y);
int[] argb = new int[]{
(color >> 24) & 0xff, //alpha
(color >> 16) & 0xff, //red
(color >> 8) & 0xff, //green
(color) & 0xff //blue
};
histogram[0][argb[0] / HISTOGRAM_BIN_SIZE]++;
histogram[1][argb[1] / HISTOGRAM_BIN_SIZE]++;
histogram[2][argb[2] / HISTOGRAM_BIN_SIZE]++;
histogram[3][argb[3] / HISTOGRAM_BIN_SIZE]++;
}
}
return histogram;
}
private String getVersionedViewAssetPath(String path)
{
String result = null;
@@ -115,112 +216,31 @@ public class ViewTest extends BaseTest
}
}
if(result == null)
result = "views/" + path;
if (result == null) result = "views/" + path;
return result;
}
private String saveBitmap(String filename, String suffix, Bitmap bitmap)
throws IOException
throws IOException
{
File dir = DatabaseHelper.getSDCardDir("test-screenshots");
if(dir == null) dir = DatabaseHelper.getFilesDir("test-screenshots");
if(dir == null) throw new RuntimeException("Could not find suitable dir for screenshots");
File dir = FileUtils.getSDCardDir("test-screenshots");
if (dir == null) dir = FileUtils.getFilesDir(targetContext,"test-screenshots");
if (dir == null) throw new RuntimeException(
"Could not find suitable dir for screenshots");
filename = filename.replaceAll("\\.png$", suffix + ".png");
String absolutePath = String.format("%s/%s", dir.getAbsolutePath(), filename);
String absolutePath =
String.format("%s/%s", dir.getAbsolutePath(), filename);
File parent = new File(absolutePath).getParentFile();
if(!parent.exists() && !parent.mkdirs())
throw new RuntimeException(String.format("Could not create dir: %s",
parent.getAbsolutePath()));
if (!parent.exists() && !parent.mkdirs()) throw new RuntimeException(
String.format("Could not create dir: %s",
parent.getAbsolutePath()));
FileOutputStream out = new FileOutputStream(absolutePath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
return absolutePath;
}
private int[][] getHistogram(Bitmap bitmap)
{
int histogram[][] = new int[4][256 / HISTOGRAM_BIN_SIZE];
for(int x = 0; x < bitmap.getWidth(); x++)
{
for(int y = 0; y < bitmap.getHeight(); y++)
{
int color = bitmap.getPixel(x, y);
int[] argb = new int[]{
(color >> 24) & 0xff, //alpha
(color >> 16) & 0xff, //red
(color >> 8) & 0xff, //green
(color ) & 0xff //blue
};
histogram[0][argb[0] / HISTOGRAM_BIN_SIZE]++;
histogram[1][argb[1] / HISTOGRAM_BIN_SIZE]++;
histogram[2][argb[2] / HISTOGRAM_BIN_SIZE]++;
histogram[3][argb[3] / HISTOGRAM_BIN_SIZE]++;
}
}
return histogram;
}
private double compareHistograms(int[][] actualHistogram, int[][] expectedHistogram)
{
long diff = 0;
long total = 0;
for(int i = 0; i < 256 / HISTOGRAM_BIN_SIZE; i ++)
{
diff += Math.abs(actualHistogram[0][i] - expectedHistogram[0][i]);
diff += Math.abs(actualHistogram[1][i] - expectedHistogram[1][i]);
diff += Math.abs(actualHistogram[2][i] - expectedHistogram[2][i]);
diff += Math.abs(actualHistogram[3][i] - expectedHistogram[3][i]);
total += actualHistogram[0][i];
total += actualHistogram[1][i];
total += actualHistogram[2][i];
total += actualHistogram[3][i];
}
return (double) diff / total / 2;
}
protected int dpToPixels(int dp)
{
return (int) UIHelper.dpToPixels(targetContext, dp);
}
protected void tap(GestureDetector.OnGestureListener view, int x, int y) throws InterruptedException
{
long now = SystemClock.uptimeMillis();
MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, dpToPixels(x),
dpToPixels(y), 0);
view.onSingleTapUp(e);
e.recycle();
}
protected void refreshData(final HabitDataView view)
{
new BaseTask()
{
@Override
protected void doInBackground()
{
view.refreshData();
}
}.execute();
try
{
waitForAsyncTasks();
}
catch (Exception e)
{
throw new RuntimeException("Time out");
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.DateUtils;
public class HabitFixtures
{
public boolean NON_DAILY_HABIT_CHECKS[] = {
true, false, false, true, true, true, false, false, true, true
};
private ModelFactory modelFactory;
private final HabitList habitList;
public HabitFixtures(ModelFactory modelFactory, HabitList habitList)
{
this.modelFactory = modelFactory;
this.habitList = habitList;
}
public Habit createEmptyHabit()
{
return createEmptyHabit(null);
}
public Habit createEmptyHabit(Long id)
{
Habit habit = modelFactory.buildHabit();
habit.setName("Meditate");
habit.setDescription("Did you meditate this morning?");
habit.setColor(3);
habit.setFrequency(Frequency.DAILY);
habit.setId(id);
habitList.add(habit);
return habit;
}
public Habit createLongHabit()
{
Habit habit = createEmptyHabit();
habit.setFrequency(new Frequency(3, 7));
habit.setColor(4);
long day = DateUtils.millisecondsInOneDay;
long today = DateUtils.getStartOfToday();
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27,
28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80,
81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120};
for (int mark : marks)
habit.getRepetitions().toggleTimestamp(today - mark * day);
return habit;
}
public Habit createShortHabit()
{
Habit habit = modelFactory.buildHabit();
habit.setName("Wake up early");
habit.setDescription("Did you wake up before 6am?");
habit.setFrequency(new Frequency(2, 3));
habitList.add(habit);
long timestamp = DateUtils.getStartOfToday();
for (boolean c : NON_DAILY_HABIT_CHECKS)
{
if (c) habit.getRepetitions().toggleTimestamp(timestamp);
timestamp -= DateUtils.millisecondsInOneDay;
}
return habit;
}
public void purgeHabits(HabitList habitList)
{
for (Habit h : habitList)
habitList.remove(h);
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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;
import android.os.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.activities.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class HabitLoggerTest extends BaseAndroidTest
{
@Test
public void testLogReminderScheduled() throws IOException
{
if (!isLogcatAvailable()) return;
long time = 1422277200000L; // 13:00 jan 26, 2015 (UTC)
Habit habit = fixtures.createEmptyHabit();
habit.setName("Write journal");
logger.logReminderScheduled(habit, time);
String expectedMsg = "Setting alarm (2015-01-26 130000): Wri\n";
assertLogcatContains(expectedMsg);
}
protected void assertLogcatContains(String expectedMsg) throws IOException
{
BaseSystem system = new BaseSystem(targetContext);
String logcat = system.getLogcat();
assertThat(logcat, containsString(expectedMsg));
}
protected boolean isLogcatAvailable()
{
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
}
}

View File

@@ -17,24 +17,24 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.unit;
package org.isoron.uhabits;
import android.os.Build;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.os.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.HabitsApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.isoron.uhabits.activities.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.IOException;
import java.io.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitsApplicationTest
@MediumTest
public class HabitsApplicationTest extends BaseAndroidTest
{
@Test
public void test_getLogcat() throws IOException
@@ -45,7 +45,8 @@ public class HabitsApplicationTest
String msg = "LOGCAT TEST";
new RuntimeException(msg).printStackTrace();
String log = HabitsApplication.getLogcat();
BaseSystem system = new BaseSystem(targetContext);
String log = system.getLogcat();
assertThat(log, containsString(msg));
}
}

View File

@@ -17,56 +17,44 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.unit.views;
package org.isoron.uhabits.activities.common.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.HabitFrequencyView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitFrequencyViewTest extends ViewTest
@MediumTest
public class FrequencyChartTest extends BaseViewTest
{
private HabitFrequencyView view;
public static final String BASE_PATH = "common/FrequencyChart/";
private FrequencyChart view;
@Override
@Before
public void setup()
public void setUp()
{
super.setup();
super.setUp();
HabitFixtures.purgeHabits();
Habit habit = HabitFixtures.createLongHabit();
fixtures.purgeHabits(habitList);
Habit habit = fixtures.createLongHabit();
view = new HabitFrequencyView(targetContext);
view.setHabit(habit);
refreshData(view);
measureView(dpToPixels(300), dpToPixels(100), view);
view = new FrequencyChart(targetContext);
view.setFrequency(habit.getRepetitions().getWeekdayFrequency());
view.setColor(ColorUtils.getAndroidTestColor(habit.getColor()));
measureView(view, dpToPixels(300), dpToPixels(100));
}
@Test
public void testRender() throws Throwable
{
assertRenders(view, "HabitFrequencyView/render.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsBackgroundTransparent(true);
assertRenders(view, "HabitFrequencyView/renderTransparent.png");
}
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(dpToPixels(200), dpToPixels(200), view);
assertRenders(view, "HabitFrequencyView/renderDifferentSize.png");
assertRenders(view, BASE_PATH + "render.png");
}
@Test
@@ -75,6 +63,20 @@ public class HabitFrequencyViewTest extends ViewTest
view.onScroll(null, null, -dpToPixels(150), 0);
view.invalidate();
assertRenders(view, "HabitFrequencyView/renderDataOffset.png");
assertRenders(view, BASE_PATH + "renderDataOffset.png");
}
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(view, dpToPixels(200), dpToPixels(200));
assertRenders(view, BASE_PATH + "renderDifferentSize.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsBackgroundTransparent(true);
assertRenders(view, BASE_PATH + "renderTransparent.png");
}
}

View File

@@ -0,0 +1,119 @@
/*
* 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.activities.common.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class HistoryChartTest extends BaseViewTest
{
private static final String BASE_PATH = "common/HistoryChart/";
private HistoryChart chart;
@Override
@Before
public void setUp()
{
super.setUp();
fixtures.purgeHabits(habitList);
Habit habit = fixtures.createLongHabit();
chart = new HistoryChart(targetContext);
chart.setCheckmarks(habit.getCheckmarks().getAllValues());
chart.setColor(ColorUtils.getAndroidTestColor(habit.getColor()));
measureView(chart, dpToPixels(400), dpToPixels(200));
}
// @Test
// public void tapDate_atInvalidLocations() throws Throwable
// {
// int expectedCheckmarkValues[] = habit.getCheckmarks().getAllValues();
//
// chart.setIsEditable(true);
// tap(chart, 118, 13); // header
// tap(chart, 336, 60); // tomorrow's square
// tap(chart, 370, 60); // right axis
// waitForAsyncTasks();
//
// int actualCheckmarkValues[] = habit.getCheckmarks().getAllValues();
// assertThat(actualCheckmarkValues, equalTo(expectedCheckmarkValues));
// }
//
// @Test
// public void tapDate_withEditableView() throws Throwable
// {
// chart.setIsEditable(true);
// tap(chart, 340, 40); // today's square
// waitForAsyncTasks();
//
// long today = DateUtils.getStartOfToday();
// assertFalse(habit.getRepetitions().containsTimestamp(today));
// }
//
// @Test
// public void tapDate_withReadOnlyView() throws Throwable
// {
// chart.setIsEditable(false);
// tap(chart, 340, 40); // today's square
// waitForAsyncTasks();
//
// long today = DateUtils.getStartOfToday();
// assertTrue(habit.getRepetitions().containsTimestamp(today));
// }
@Test
public void testRender() throws Throwable
{
assertRenders(chart, BASE_PATH + "render.png");
}
@Test
public void testRender_withDataOffset() throws Throwable
{
chart.onScroll(null, null, -dpToPixels(150), 0);
chart.invalidate();
assertRenders(chart, BASE_PATH + "renderDataOffset.png");
}
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(chart, dpToPixels(200), dpToPixels(200));
assertRenders(chart, BASE_PATH + "renderDifferentSize.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
chart.setIsBackgroundTransparent(true);
assertRenders(chart, BASE_PATH + "renderTransparent.png");
}
}

View File

@@ -17,35 +17,37 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.unit.views;
package org.isoron.uhabits.activities.common.views;
import android.graphics.Color;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.graphics.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.views.RingView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.isoron.uhabits.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.IOException;
import java.io.*;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class RingViewTest extends ViewTest
@MediumTest
public class RingViewTest extends BaseViewTest
{
private static final String BASE_PATH = "common/RingView/";
private RingView view;
@Override
@Before
public void setup()
public void setUp()
{
super.setup();
super.setUp();
view = new RingView(targetContext);
view.setPercentage(0.6f);
view.setText("60%");
view.setColor(ColorHelper.CSV_PALETTE[0]);
view.setColor(ColorUtils.getAndroidTestColor(0));
view.setBackgroundColor(Color.WHITE);
view.setThickness(dpToPixels(3));
}
@@ -53,17 +55,17 @@ public class RingViewTest extends ViewTest
@Test
public void testRender_base() throws IOException
{
measureView(dpToPixels(100), dpToPixels(100), view);
assertRenders(view, "RingView/render.png");
measureView(view, dpToPixels(100), dpToPixels(100));
assertRenders(view, BASE_PATH + "render.png");
}
@Test
public void testRender_withDifferentParams() throws IOException
{
view.setPercentage(0.25f);
view.setColor(ColorHelper.CSV_PALETTE[5]);
view.setColor(ColorUtils.getAndroidTestColor(5));
measureView(dpToPixels(200), dpToPixels(200), view);
assertRenders(view, "RingView/renderDifferentParams.png");
measureView(view, dpToPixels(200), dpToPixels(200));
assertRenders(view, BASE_PATH + "renderDifferentParams.png");
}
}

View File

@@ -17,60 +17,50 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.unit.views;
package org.isoron.uhabits.activities.common.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import android.util.*;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.HabitScoreView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitScoreViewTest extends ViewTest
@MediumTest
public class ScoreChartTest extends BaseViewTest
{
private static final String BASE_PATH = "common/ScoreChart/";
private Habit habit;
private HabitScoreView view;
private ScoreChart view;
@Override
@Before
public void setup()
public void setUp()
{
super.setup();
super.setUp();
HabitFixtures.purgeHabits();
habit = HabitFixtures.createLongHabit();
fixtures.purgeHabits(habitList);
habit = fixtures.createLongHabit();
view = new HabitScoreView(targetContext);
view.setHabit(habit);
view = new ScoreChart(targetContext);
view.setScores(habit.getScores().toList());
view.setColor(ColorUtils.getColor(targetContext, habit.getColor()));
view.setBucketSize(7);
refreshData(view);
measureView(dpToPixels(300), dpToPixels(200), view);
measureView(view, dpToPixels(300), dpToPixels(200));
}
@Test
public void testRender() throws Throwable
{
Log.d("HabitScoreViewTest", String.format("height=%d", dpToPixels(100)));
assertRenders(view, "HabitScoreView/render.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsTransparencyEnabled(true);
assertRenders(view, "HabitScoreView/renderTransparent.png");
}
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(dpToPixels(200), dpToPixels(200), view);
assertRenders(view, "HabitScoreView/renderDifferentSize.png");
Log.d("HabitScoreViewTest",
String.format("height=%d", dpToPixels(100)));
assertRenders(view, BASE_PATH + "render.png");
}
@Test
@@ -79,26 +69,40 @@ public class HabitScoreViewTest extends ViewTest
view.onScroll(null, null, -dpToPixels(150), 0);
view.invalidate();
assertRenders(view, "HabitScoreView/renderDataOffset.png");
assertRenders(view, BASE_PATH + "renderDataOffset.png");
}
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(view, dpToPixels(200), dpToPixels(200));
assertRenders(view, BASE_PATH + "renderDifferentSize.png");
}
@Test
public void testRender_withMonthlyBucket() throws Throwable
{
view.setScores(habit.getScores().groupBy(DateUtils.TruncateField.MONTH));
view.setBucketSize(30);
view.refreshData();
view.invalidate();
assertRenders(view, "HabitScoreView/renderMonthly.png");
assertRenders(view, BASE_PATH + "renderMonthly.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsTransparencyEnabled(true);
assertRenders(view, BASE_PATH + "renderTransparent.png");
}
@Test
public void testRender_withYearlyBucket() throws Throwable
{
view.setScores(habit.getScores().groupBy(DateUtils.TruncateField.YEAR));
view.setBucketSize(365);
view.refreshData();
view.invalidate();
assertRenders(view, "HabitScoreView/renderYearly.png");
assertRenders(view, BASE_PATH + "renderYearly.png");
}
}

View File

@@ -17,58 +17,57 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.unit.views;
package org.isoron.uhabits.activities.common.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.HabitStreakView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitStreakViewTest extends ViewTest
@MediumTest
public class StreakChartTest extends BaseViewTest
{
private HabitStreakView view;
private static final String BASE_PATH = "common/StreakChart/";
private StreakChart view;
@Override
@Before
public void setup()
public void setUp()
{
super.setup();
super.setUp();
HabitFixtures.purgeHabits();
Habit habit = HabitFixtures.createLongHabit();
fixtures.purgeHabits(habitList);
Habit habit = fixtures.createLongHabit();
view = new HabitStreakView(targetContext);
measureView(dpToPixels(300), dpToPixels(100), view);
view.setHabit(habit);
refreshData(view);
view = new StreakChart(targetContext);
view.setColor(ColorUtils.getAndroidTestColor(habit.getColor()));
view.setStreaks(habit.getStreaks().getBest(5));
measureView(view, dpToPixels(300), dpToPixels(100));
}
@Test
public void testRender() throws Throwable
{
assertRenders(view, "HabitStreakView/render.png");
assertRenders(view, BASE_PATH + "render.png");
}
@Test
public void testRender_withSmallSize() throws Throwable
{
measureView(view, dpToPixels(100), dpToPixels(100));
assertRenders(view, BASE_PATH + "renderSmallSize.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsBackgroundTransparent(true);
assertRenders(view, "HabitStreakView/renderTransparent.png");
}
@Test
public void testRender_withSmallSize() throws Throwable
{
measureView(dpToPixels(100), dpToPixels(100), view);
refreshData(view);
assertRenders(view, "HabitStreakView/renderSmallSize.png");
assertRenders(view, BASE_PATH + "renderTransparent.png");
}
}

View File

@@ -0,0 +1,187 @@
/*
* 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.activities.habits.list.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.*;
import java.util.concurrent.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class CheckmarkButtonViewTest extends BaseViewTest
{
public static final String PATH = "habits/list/CheckmarkButtonView/";
private CountDownLatch latch;
private CheckmarkButtonView view;
@Override
@Before
public void setUp()
{
super.setUp();
setSimilarityCutoff(0.03f);
latch = new CountDownLatch(1);
view = new CheckmarkButtonView(targetContext);
view.setValue(Checkmark.UNCHECKED);
view.setColor(ColorUtils.getAndroidTestColor(7));
measureView(view, dpToPixels(40), dpToPixels(40));
}
@Test
public void testRender_explicitCheck() throws Exception
{
view.setValue(Checkmark.CHECKED_EXPLICITLY);
assertRendersCheckedExplicitly();
}
@Test
public void testRender_implicitCheck() throws Exception
{
view.setValue(Checkmark.CHECKED_IMPLICITLY);
assertRendersCheckedImplicitly();
}
@Test
public void testRender_unchecked() throws Exception
{
view.setValue(Checkmark.UNCHECKED);
assertRendersUnchecked();
}
protected void assertRendersCheckedExplicitly() throws IOException
{
assertRenders(view, PATH + "render_explicit_check.png");
}
protected void assertRendersCheckedImplicitly() throws IOException
{
assertRenders(view, PATH + "render_implicit_check.png");
}
protected void assertRendersUnchecked() throws IOException
{
assertRenders(view, PATH + "render_unchecked.png");
}
// @Test
// public void testLongClick() throws Exception
// {
// setOnToggleListener();
// view.performLongClick();
// waitForLatch();
// assertRendersCheckedExplicitly();
// }
//
// @Test
// public void testClick_withShortToggle_fromUnchecked() throws Exception
// {
// Preferences.getInstance().setShortToggleEnabled(true);
// view.setValue(Checkmark.UNCHECKED);
// setOnToggleListenerAndPerformClick();
// assertRendersCheckedExplicitly();
// }
//
// @Test
// public void testClick_withShortToggle_fromChecked() throws Exception
// {
// Preferences.getInstance().setShortToggleEnabled(true);
// view.setValue(Checkmark.CHECKED_EXPLICITLY);
// setOnToggleListenerAndPerformClick();
// assertRendersUnchecked();
// }
//
// @Test
// public void testClick_withShortToggle_withoutListener() throws Exception
// {
// Preferences.getInstance().setShortToggleEnabled(true);
// view.setValue(Checkmark.CHECKED_EXPLICITLY);
// view.setController(null);
// view.performClick();
// assertRendersUnchecked();
// }
//
// protected void setOnToggleListenerAndPerformClick() throws InterruptedException
// {
// setOnToggleListener();
// view.performClick();
// waitForLatch();
// }
//
// @Test
// public void testClick_withoutShortToggle() throws Exception
// {
// Preferences.getInstance().setShortToggleEnabled(false);
// setOnInvalidToggleListener();
// view.performClick();
// waitForLatch();
// assertRendersUnchecked();
// }
// protected void setOnInvalidToggleListener()
// {
// view.setController(new CheckmarkButtonView.Controller()
// {
// @Override
// public void onToggleCheckmark(CheckmarkButtonView view, long timestamp)
// {
// fail();
// }
//
// @Override
// public void onInvalidToggle(CheckmarkButtonView v)
// {
// assertThat(v, equalTo(view));
// latch.countDown();
// }
// });
// }
// protected void setOnToggleListener()
// {
// view.setController(new CheckmarkButtonView.Controller()
// {
// @Override
// public void onToggleCheckmark(CheckmarkButtonView v, long t)
// {
// assertThat(v, equalTo(view));
// assertThat(t, equalTo(DateUtils.getStartOfToday()));
// latch.countDown();
// }
//
// @Override
// public void onInvalidToggle(CheckmarkButtonView view)
// {
// fail();
// }
// });
// }
}

View File

@@ -0,0 +1,98 @@
/*
* 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.activities.habits.list.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.models.Checkmark;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.BaseViewTest;
import org.isoron.uhabits.utils.ColorUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class CheckmarkPanelViewTest extends BaseViewTest
{
public static final String PATH = "habits/list/CheckmarkPanelView/";
private CountDownLatch latch;
private CheckmarkPanelView view;
private int checkmarks[];
@Override
@Before
public void setUp()
{
super.setUp();
setSimilarityCutoff(0.03f);
prefs.setShouldReverseCheckmarks(false);
Habit habit = fixtures.createEmptyHabit();
latch = new CountDownLatch(1);
checkmarks = new int[]{
Checkmark.CHECKED_EXPLICITLY, Checkmark.UNCHECKED,
Checkmark.CHECKED_IMPLICITLY, Checkmark.CHECKED_EXPLICITLY};
view = new CheckmarkPanelView(targetContext);
view.setHabit(habit);
view.setCheckmarkValues(checkmarks);
view.setButtonCount(4);
view.setColor(ColorUtils.getAndroidTestColor(7));
measureView(view, dpToPixels(200), dpToPixels(200));
}
// protected void waitForLatch() throws InterruptedException
// {
// assertTrue("Latch timeout", latch.await(1, TimeUnit.SECONDS));
// }
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
// @Test
// public void testToggleCheckmark_withLeftToRight() throws Exception
// {
// setToggleListener();
// view.getButton(1).performToggle();
// waitForLatch();
// }
//
// @Test
// public void testToggleCheckmark_withReverseCheckmarks() throws Exception
// {
// prefs.setShouldReverseCheckmarks(true);
// view.setCheckmarkValues(checkmarks); // refresh after preference change
//
// setToggleListener();
// view.getButton(2).performToggle();
// waitForLatch();
// }
}

View File

@@ -0,0 +1,91 @@
/*
* 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.activities.habits.list.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import static org.mockito.Mockito.mock;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class HabitCardViewTest extends BaseViewTest
{
private HabitCardView view;
public static final String PATH = "habits/list/HabitCardView/";
private HabitCardView.Controller controller;
private Habit habit;
@Override
public void setUp()
{
super.setUp();
setTheme(R.style.AppBaseTheme);
habit = fixtures.createLongHabit();
CheckmarkList checkmarks = habit.getCheckmarks();
long today = DateUtils.getStartOfToday();
long day = DateUtils.millisecondsInOneDay;
int[] values = checkmarks.getValues(today - 5 * day, today);
controller = mock(HabitCardView.Controller.class);
view = new HabitCardView(targetContext);
view.setHabit(habit);
view.setCheckmarkValues(values);
view.setSelected(false);
view.setScore(habit.getScores().getTodayValue());
view.setController(controller);
measureView(view, dpToPixels(400), dpToPixels(50));
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
@Test
public void testRender_selected() throws Exception
{
view.setSelected(true);
measureView(view, dpToPixels(400), dpToPixels(50));
assertRenders(view, PATH + "render_selected.png");
}
@Test
public void testChangeModel() throws Exception
{
habit.setName("Wake up early");
habit.setColor(2);
habit.getObservable().notifyListeners();
assertRenders(view, PATH + "render_changed.png");
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.activities.habits.list.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.habits.list.model.*;
import org.junit.*;
import org.junit.runner.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class HintViewTest extends BaseViewTest
{
public static final String PATH = "habits/list/HintView/";
private HintView view;
private HintList list;
@Before
@Override
public void setUp()
{
super.setUp();
view = new HintView(targetContext);
list = mock(HintList.class);
view.setHints(list);
measureView(view, 400, 200);
String text =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
when(list.shouldShow()).thenReturn(true);
when(list.pop()).thenReturn(text);
view.showNext();
skipAnimation(view);
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
@Test
public void testClick() throws Exception
{
assertThat(view.getAlpha(), equalTo(1f));
view.performClick();
skipAnimation(view);
assertThat(view.getAlpha(), equalTo(0f));
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.activities.habits.show.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import android.view.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class FrequencyCardTest extends BaseViewTest
{
public static final String PATH = "habits/show/FrequencyCard/";
private FrequencyCard view;
private Habit habit;
@Before
@Override
public void setUp()
{
super.setUp();
habit = fixtures.createLongHabit();
view = (FrequencyCard) LayoutInflater
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.frequencyCard);
view.setHabit(habit);
view.refreshData();
measureView(view, 800, 600);
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.activities.habits.show.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import android.view.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class HistoryCardTest extends BaseViewTest
{
public static final String PATH = "habits/show/HistoryCard/";
private HistoryCard view;
private Habit habit;
@Before
@Override
public void setUp()
{
super.setUp();
habit = fixtures.createLongHabit();
view = (HistoryCard) LayoutInflater
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.historyCard);
view.setHabit(habit);
view.refreshData();
measureView(view, 800, 600);
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.activities.habits.show.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import android.view.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class OverviewCardTest extends BaseViewTest
{
public static final String PATH = "habits/show/OverviewCard/";
private OverviewCard view;
private Habit habit;
@Before
@Override
public void setUp()
{
super.setUp();
habit = fixtures.createLongHabit();
view = (OverviewCard) LayoutInflater
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.overviewCard);
view.setHabit(habit);
view.refreshData();
measureView(view, 800, 300);
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.activities.habits.show.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import android.view.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class ScoreCardTest extends BaseViewTest
{
public static final String PATH = "habits/show/ScoreCard/";
private ScoreCard view;
private Habit habit;
@Before
@Override
public void setUp()
{
super.setUp();
habit = fixtures.createLongHabit();
view = (ScoreCard) LayoutInflater
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.scoreCard);
view.setHabit(habit);
view.refreshData();
measureView(view, 800, 600);
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.activities.habits.show.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import android.view.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class StreakCardTest extends BaseViewTest
{
public static final String PATH = "habits/show/StreakCard/";
private StreakCard view;
private Habit habit;
@Before
@Override
public void setUp()
{
super.setUp();
habit = fixtures.createLongHabit();
view = (StreakCard) LayoutInflater
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.streakCard);
view.setHabit(habit);
view.refreshData();
measureView(view, 800, 600);
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.activities.habits.show.views;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import android.view.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.junit.*;
import org.junit.runner.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class SubtitleCardTest extends BaseViewTest
{
public static final String PATH = "habits/show/SubtitleCard/";
private SubtitleCard view;
private Habit habit;
@Before
@Override
public void setUp()
{
super.setUp();
habit = fixtures.createLongHabit();
habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
view = (SubtitleCard) LayoutInflater
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.subtitleCard);
view.setHabit(habit);
view.refreshData();
measureView(view, 800, 200);
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
}

View File

@@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.ui;
package org.isoron.uhabits.espresso;
import android.preference.Preference;
import android.view.View;
@@ -39,7 +39,7 @@ public class HabitMatchers
@Override
public boolean matchesSafely(Habit habit)
{
return habit.name.equals(name);
return habit.getName().equals(name);
}
@Override
@@ -51,7 +51,7 @@ public class HabitMatchers
@Override
public void describeMismatchSafely(Habit habit, Description description)
{
description.appendText("was ").appendText(habit.name);
description.appendText("was ").appendText(habit.getName());
}
};
}

View File

@@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.ui;
package org.isoron.uhabits.espresso;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
@@ -61,7 +61,7 @@ public class HabitViewActions
@Override
public void perform(UiController uiController, View view)
{
if (view.getId() != R.id.llButtons)
if (view.getId() != R.id.checkmarkPanel)
throw new InvalidParameterException("View must have id llButtons");
LinearLayout llButtons = (LinearLayout) view;

View File

@@ -0,0 +1,199 @@
/*
* 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.espresso;
import android.support.test.espresso.*;
import android.support.test.espresso.contrib.*;
import org.hamcrest.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.*;
import java.util.*;
import static android.support.test.espresso.Espresso.*;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.*;
import static android.support.test.espresso.assertion.ViewAssertions.*;
import static android.support.test.espresso.matcher.RootMatchers.*;
import static android.support.test.espresso.matcher.ViewMatchers.Visibility.*;
import static android.support.test.espresso.matcher.ViewMatchers.*;
import static org.hamcrest.Matchers.*;
public class MainActivityActions
{
public static String addHabit()
{
return addHabit(false);
}
public static String addHabit(boolean openDialogs)
{
String name = "New Habit " + new Random().nextInt(1000000);
String description = "Did you perform your new habit today?";
String num = "4";
String den = "8";
onView(withId(R.id.actionAdd)).perform(click());
typeHabitData(name, description, num, den);
if (openDialogs)
{
onView(withId(R.id.buttonPickColor)).perform(click());
pressBack();
onView(withId(R.id.tvReminderTime)).perform(click());
onView(withText("Done")).perform(click());
onView(withId(R.id.tvReminderDays)).perform(click());
onView(withText("OK")).perform(click());
}
onView(withId(R.id.buttonSave)).perform(click());
onData(Matchers.allOf(is(instanceOf(Habit.class)),
HabitMatchers.withName(name))).onChildView(withId(R.id.label));
return name;
}
public static void assertHabitExists(String name)
{
List<String> names = new LinkedList<>();
names.add(name);
assertHabitsExist(names);
}
public static void assertHabitsDontExist(List<String> names)
{
for (String name : names)
onView(withId(R.id.listView)).check(matches(Matchers.not(
HabitMatchers.containsHabit(HabitMatchers.withName(name)))));
}
public static void assertHabitsExist(List<String> names)
{
for (String name : names)
onData(Matchers.allOf(is(instanceOf(Habit.class)),
HabitMatchers.withName(name))).check(matches(isDisplayed()));
}
private static void clickHiddenMenuItem(int stringId)
{
try
{
// Try the ActionMode overflow menu first
onView(allOf(withContentDescription("More options"), withParent(
withParent(withClassName(containsString("Action")))))).perform(
click());
}
catch (Exception e1)
{
// Try the toolbar overflow menu
onView(allOf(withContentDescription("More options"), withParent(
withParent(withClassName(containsString("Toolbar")))))).perform(
click());
}
onView(withText(stringId)).perform(click());
}
public static void clickMenuItem(int stringId)
{
try
{
onView(withText(stringId)).perform(click());
}
catch (Exception e1)
{
try
{
onView(withContentDescription(stringId)).perform(click());
}
catch (Exception e2)
{
clickHiddenMenuItem(stringId);
}
}
}
public static void clickSettingsItem(String text)
{
onView(withClassName(containsString("RecyclerView"))).perform(
RecyclerViewActions.actionOnItem(
hasDescendant(withText(containsString(text))), click()));
}
public static void deleteHabit(String name)
{
deleteHabits(Collections.singletonList(name));
}
public static void deleteHabits(List<String> names)
{
selectHabits(names);
clickMenuItem(R.string.delete);
onView(withText("OK")).perform(click());
assertHabitsDontExist(names);
}
public static void selectHabit(String name)
{
selectHabits(Collections.singletonList(name));
}
public static void selectHabits(List<String> names)
{
boolean first = true;
for (String name : names)
{
onData(Matchers.allOf(is(instanceOf(Habit.class)),
HabitMatchers.withName(name)))
.onChildView(withId(R.id.label))
.perform(first ? longClick() : click());
first = false;
}
}
public static void typeHabitData(String name,
String description,
String num,
String den)
{
onView(withId(R.id.tvName)).perform(replaceText(name));
onView(withId(R.id.tvDescription)).perform(replaceText(description));
try
{
onView(allOf(withId(R.id.sFrequency),
withEffectiveVisibility(VISIBLE))).perform(click());
onData(allOf(instanceOf(String.class), startsWith("Custom")))
.inRoot(isPlatformPopup())
.perform(click());
}
catch (NoMatchingViewException e)
{
// ignored
}
onView(withId(R.id.tvFreqNum)).perform(replaceText(num));
onView(withId(R.id.tvFreqDen)).perform(replaceText(den));
}
}

View File

@@ -0,0 +1,317 @@
/*
* 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.espresso;
import android.app.*;
import android.content.*;
import android.support.test.*;
import android.support.test.espresso.*;
import android.support.test.espresso.intent.rule.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.hamcrest.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.habits.list.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.util.*;
import static android.support.test.espresso.Espresso.*;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.*;
import static android.support.test.espresso.assertion.ViewAssertions.*;
import static android.support.test.espresso.intent.Intents.*;
import static android.support.test.espresso.intent.matcher.IntentMatchers.*;
import static android.support.test.espresso.matcher.ViewMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.isoron.uhabits.espresso.HabitViewActions.*;
import static org.isoron.uhabits.espresso.MainActivityActions.*;
import static org.isoron.uhabits.espresso.ShowHabitActivityActions.*;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainTest
{
private SystemHelper sys;
@Rule
public IntentsTestRule<ListHabitsActivity> activityRule =
new IntentsTestRule<>(ListHabitsActivity.class);
@Before
public void setup()
{
Context context =
InstrumentationRegistry.getInstrumentation().getContext();
sys = new SystemHelper(context);
sys.disableAllAnimations();
sys.acquireWakeLock();
sys.unlockScreen();
Instrumentation.ActivityResult okResult =
new Instrumentation.ActivityResult(Activity.RESULT_OK,
new Intent());
intending(hasAction(equalTo(Intent.ACTION_SEND))).respondWith(okResult);
intending(hasAction(equalTo(Intent.ACTION_SENDTO))).respondWith(
okResult);
intending(hasAction(equalTo(Intent.ACTION_VIEW))).respondWith(okResult);
skipTutorial();
}
public void skipTutorial()
{
try
{
for (int i = 0; i < 10; i++)
onView(allOf(withClassName(endsWith("AppCompatImageButton")),
isDisplayed())).perform(click());
}
catch (NoMatchingViewException e)
{
// ignored
}
}
@After
public void tearDown()
{
sys.releaseWakeLock();
}
/**
* User opens menu, clicks about, sees about screen.
*/
@Test
public void testAbout()
{
clickMenuItem(R.string.about);
onView(isRoot()).perform(swipeUp());
}
/**
* User creates a habit, toggles a bunch of checkmarks, clicks the habit to
* open the statistics screen, scrolls down to some views, then scrolls the
* views backwards and forwards in time.
*/
@Test
public void testAddHabitAndViewStats() throws InterruptedException
{
String name = addHabit(true);
onData(Matchers.allOf(is(instanceOf(Habit.class)),
HabitMatchers.withName(name)))
.onChildView(withId(R.id.checkmarkPanel))
.perform(toggleAllCheckmarks());
Thread.sleep(1200);
onData(Matchers.allOf(is(instanceOf(Habit.class)),
HabitMatchers.withName(name)))
.onChildView(withId(R.id.label))
.perform(click());
onView(withId(R.id.scoreView)).perform(scrollTo(), swipeRight());
onView(withId(R.id.frequencyChart)).perform(scrollTo(), swipeRight());
}
/**
* User opens the app, clicks the add button, types some bogus information,
* tries to save, dialog displays an error.
*/
@Test
public void testAddInvalidHabit()
{
onView(withId(R.id.actionAdd)).perform(click());
typeHabitData("", "", "15", "7");
onView(withId(R.id.buttonSave)).perform(click());
onView(withId(R.id.tvName)).check(matches(isDisplayed()));
}
/**
* User opens the app, creates some habits, selects them, archives them,
* select 'show archived' on the menu, selects the previously archived
* habits and then deletes them.
*/
@Test
public void testArchiveHabits()
{
List<String> names = new LinkedList<>();
for (int i = 0; i < 3; i++)
names.add(addHabit());
selectHabits(names);
clickMenuItem(R.string.archive);
assertHabitsDontExist(names);
clickMenuItem(R.string.show_archived);
assertHabitsExist(names);
selectHabits(names);
clickMenuItem(R.string.unarchive);
clickMenuItem(R.string.show_archived);
assertHabitsExist(names);
deleteHabits(names);
}
/**
* User creates a habit, selects the habit, clicks edit button, changes some
* information about the habit, click save button, sees changes on the main
* window, selects habit again, changes color, then deletes the habit.
*/
@Test
public void testEditHabit()
{
String name = addHabit();
onData(Matchers.allOf(is(instanceOf(Habit.class)),
HabitMatchers.withName(name)))
.onChildView(withId(R.id.label))
.perform(longClick());
clickMenuItem(R.string.edit);
String modifiedName = "Modified " + new Random().nextInt(10000);
typeHabitData(modifiedName, "", "1", "1");
onView(withId(R.id.buttonSave)).perform(click());
assertHabitExists(modifiedName);
selectHabit(modifiedName);
clickMenuItem(R.string.color_picker_default_title);
pressBack();
deleteHabit(modifiedName);
}
/**
* User creates a habit, opens statistics page, clicks button to edit
* history, adds some checkmarks, closes dialog, sees the modified history
* calendar.
*/
@Test
public void testEditHistory()
{
String name = addHabit();
onData(Matchers.allOf(is(instanceOf(Habit.class)),
HabitMatchers.withName(name)))
.onChildView(withId(R.id.label))
.perform(click());
openHistoryEditor();
onView(withClassName(endsWith("HabitHistoryView"))).perform(
clickAtRandomLocations(20));
pressBack();
onView(withId(R.id.historyChart)).perform(scrollTo(), swipeRight(),
swipeLeft());
}
/**
* User creates a habit, opens settings, clicks export as CSV, is asked what
* activity should handle the file.
*/
@Test
public void testExportCSV()
{
addHabit();
clickMenuItem(R.string.settings);
clickSettingsItem("Export as CSV");
intended(hasAction(Intent.ACTION_SEND));
}
/**
* User creates a habit, exports full backup, deletes the habit, restores
* backup, sees that the previously created habit has appeared back.
*/
@Test
public void testExportImportDB()
{
String name = addHabit();
clickMenuItem(R.string.settings);
String date =
DateFormats.getBackupDateFormat().format(DateUtils.getLocalTime());
date = date.substring(0, date.length() - 2);
clickSettingsItem("Export full backup");
intended(hasAction(Intent.ACTION_SEND));
deleteHabit(name);
clickMenuItem(R.string.settings);
clickSettingsItem("Import data");
onData(
allOf(is(instanceOf(String.class)), startsWith("Backups"))).perform(
click());
onData(
allOf(is(instanceOf(String.class)), containsString(date))).perform(
click());
selectHabit(name);
}
/**
* User opens the settings and generates a bug report.
*/
@Test
public void testGenerateBugReport()
{
clickMenuItem(R.string.settings);
clickSettingsItem("Generate bug report");
intended(hasAction(Intent.ACTION_SEND));
}
/**
* User opens menu, clicks Help, sees website.
*/
@Test
public void testHelp()
{
clickMenuItem(R.string.help);
intended(hasAction(Intent.ACTION_VIEW));
}
/**
* User opens menu, clicks settings, sees settings screen.
*/
@Test
public void testSettings()
{
clickMenuItem(R.string.settings);
}
}

View File

@@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.ui;
package org.isoron.uhabits.espresso;
import android.support.test.espresso.matcher.ViewMatchers;
@@ -31,7 +31,7 @@ public class ShowHabitActivityActions
{
public static void openHistoryEditor()
{
onView(ViewMatchers.withId(R.id.btEditHistory))
onView(ViewMatchers.withId(R.id.edit))
.perform(scrollTo(), click());
}
}

View File

@@ -1,4 +1,23 @@
package org.isoron.uhabits.ui;
/*
* 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.espresso;
import android.app.KeyguardManager;
import android.content.Context;

View File

@@ -17,80 +17,52 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.unit.io;
package org.isoron.uhabits.io;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.content.*;
import android.support.test.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.io.HabitsCSVExporter;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.*;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitsCSVExporterTest extends BaseTest
@MediumTest
public class HabitsCSVExporterTest extends BaseAndroidTest
{
private File baseDir;
@Before
public void setup()
public void setUp()
{
super.setup();
super.setUp();
HabitFixtures.purgeHabits();
HabitFixtures.createShortHabit();
HabitFixtures.createEmptyHabit();
fixtures.purgeHabits(habitList);
fixtures.createShortHabit();
fixtures.createEmptyHabit();
Context targetContext = InstrumentationRegistry.getTargetContext();
baseDir = targetContext.getCacheDir();
}
private void unzip(File file) throws IOException
{
ZipFile zip = new ZipFile(file);
Enumeration<? extends ZipEntry> e = zip.entries();
while(e.hasMoreElements())
{
ZipEntry entry = e.nextElement();
InputStream stream = zip.getInputStream(entry);
String outputFilename = String.format("%s/%s", baseDir.getAbsolutePath(),
entry.getName());
File outputFile = new File(outputFilename);
File parent = outputFile.getParentFile();
if(parent != null) parent.mkdirs();
DatabaseHelper.copy(stream, outputFile);
}
zip.close();
}
@Test
public void testExportCSV() throws IOException
{
List<Habit> habits = Habit.getAll(true);
List<Habit> selected = new LinkedList<>();
for (Habit h : habitList) selected.add(h);
HabitsCSVExporter exporter = new HabitsCSVExporter(habits, baseDir);
HabitsCSVExporter exporter =
new HabitsCSVExporter(habitList, selected, baseDir);
String filename = exporter.writeArchive();
assertAbsolutePathExists(filename);
@@ -103,16 +75,45 @@ public class HabitsCSVExporterTest extends BaseTest
assertPathExists("001 Wake up early/Scores.csv");
assertPathExists("002 Meditate/Checkmarks.csv");
assertPathExists("002 Meditate/Scores.csv");
}
private void assertPathExists(String s)
{
assertAbsolutePathExists(String.format("%s/%s", baseDir.getAbsolutePath(), s));
assertPathExists("Checkmarks.csv");
assertPathExists("Scores.csv");
}
private void assertAbsolutePathExists(String s)
{
File file = new File(s);
assertTrue(String.format("File %s should exist", file.getAbsolutePath()), file.exists());
assertTrue(
String.format("File %s should exist", file.getAbsolutePath()),
file.exists());
}
private void assertPathExists(String s)
{
assertAbsolutePathExists(
String.format("%s/%s", baseDir.getAbsolutePath(), s));
}
private void unzip(File file) throws IOException
{
ZipFile zip = new ZipFile(file);
Enumeration<? extends ZipEntry> e = zip.entries();
while (e.hasMoreElements())
{
ZipEntry entry = e.nextElement();
InputStream stream = zip.getInputStream(entry);
String outputFilename =
String.format("%s/%s", baseDir.getAbsolutePath(),
entry.getName());
File outputFile = new File(outputFilename);
File parent = outputFile.getParentFile();
if (parent != null) parent.mkdirs();
FileUtils.copy(stream, outputFile);
}
zip.close();
}
}

View File

@@ -0,0 +1,159 @@
/*
* 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.io;
import android.content.*;
import android.support.test.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.*;
import java.util.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class ImportTest extends BaseAndroidTest
{
private Context context;
@Override
@Before
public void setUp()
{
super.setUp();
DateUtils.setFixedLocalTime(null);
fixtures.purgeHabits(habitList);
context = InstrumentationRegistry.getInstrumentation().getContext();
}
@Test
public void testHabitBullCSV() throws IOException
{
importFromFile("habitbull.csv");
assertThat(habitList.size(), equalTo(4));
Habit habit = habitList.getByPosition(0);
assertThat(habit.getName(), equalTo("Breed dragons"));
assertThat(habit.getDescription(), equalTo("with love and fire"));
assertThat(habit.getFrequency(), equalTo(Frequency.DAILY));
assertTrue(containsRepetition(habit, 2016, 3, 18));
assertTrue(containsRepetition(habit, 2016, 3, 19));
assertFalse(containsRepetition(habit, 2016, 3, 20));
}
@Test
public void testLoopDB() throws IOException
{
importFromFile("loop.db");
assertThat(habitList.size(), equalTo(9));
Habit habit = habitList.getByPosition(0);
assertThat(habit.getName(), equalTo("Wake up early"));
assertThat(habit.getFrequency(),
equalTo(Frequency.THREE_TIMES_PER_WEEK));
assertTrue(containsRepetition(habit, 2016, 3, 14));
assertTrue(containsRepetition(habit, 2016, 3, 16));
assertFalse(containsRepetition(habit, 2016, 3, 17));
}
@Test
public void testRewireDB() throws IOException
{
importFromFile("rewire.db");
assertThat(habitList.size(), equalTo(3));
Habit habit = habitList.getByPosition(0);
assertThat(habit.getName(), equalTo("Wake up early"));
assertThat(habit.getFrequency(),
equalTo(Frequency.THREE_TIMES_PER_WEEK));
assertFalse(habit.hasReminder());
assertFalse(containsRepetition(habit, 2015, 12, 31));
assertTrue(containsRepetition(habit, 2016, 1, 18));
assertTrue(containsRepetition(habit, 2016, 1, 28));
assertFalse(containsRepetition(habit, 2016, 3, 10));
habit = habitList.getByPosition(1);
assertThat(habit.getName(), equalTo("brush teeth"));
assertThat(habit.getFrequency(),
equalTo(Frequency.THREE_TIMES_PER_WEEK));
assertThat(habit.hasReminder(), equalTo(true));
Reminder reminder = habit.getReminder();
assertThat(reminder.getHour(), equalTo(8));
assertThat(reminder.getMinute(), equalTo(0));
boolean[] reminderDays = { false, true, true, true, true, true, false };
assertThat(reminder.getDays().toArray(), equalTo(reminderDays));
}
@Test
public void testTickmateDB() throws IOException
{
importFromFile("tickmate.db");
assertThat(habitList.size(), equalTo(3));
Habit h = habitList.getByPosition(0);
assertThat(h.getName(), equalTo("Vegan"));
assertTrue(containsRepetition(h, 2016, 1, 24));
assertTrue(containsRepetition(h, 2016, 2, 5));
assertTrue(containsRepetition(h, 2016, 3, 18));
assertFalse(containsRepetition(h, 2016, 3, 14));
}
private boolean containsRepetition(Habit h, int year, int month, int day)
{
GregorianCalendar date = DateUtils.getStartOfTodayCalendar();
date.set(year, month - 1, day);
return h.getRepetitions().containsTimestamp(date.getTimeInMillis());
}
private void copyAssetToFile(String assetPath, File dst) throws IOException
{
InputStream in = context.getAssets().open(assetPath);
FileUtils.copy(in, dst);
}
private void importFromFile(String assetFilename) throws IOException
{
File file = File.createTempFile("asset", "");
copyAssetToFile(assetFilename, file);
assertTrue(file.exists());
assertTrue(file.canRead());
GenericImporter importer = component.getGenericImporter();
assertThat(importer.canHandle(file), is(true));
importer.importHabitsFromFile(file);
file.delete();
}
}

View File

@@ -0,0 +1,240 @@
/*
* 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;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.hamcrest.*;
import org.isoron.uhabits.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.*;
import java.util.*;
import static junit.framework.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.isoron.uhabits.models.HabitList.Order.*;
@SuppressWarnings("JavaDoc")
@RunWith(AndroidJUnit4.class)
@MediumTest
public class HabitListTest extends BaseAndroidTest
{
private ArrayList<Habit> habitsArray;
private HabitList activeHabits;
private HabitList reminderHabits;
@Override
public void setUp()
{
super.setUp();
habitList.removeAll();
habitsArray = new ArrayList<>();
for (int i = 0; i < 10; i++)
{
Habit habit = fixtures.createEmptyHabit((long) i);
habitsArray.add(habit);
if (i % 3 == 0)
habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
habitList.update(habit);
}
habitsArray.get(0).setArchived(true);
habitsArray.get(1).setArchived(true);
habitsArray.get(4).setArchived(true);
habitsArray.get(7).setArchived(true);
activeHabits = habitList.getFiltered(new HabitMatcherBuilder().build());
reminderHabits = habitList.getFiltered(new HabitMatcherBuilder()
.setArchivedAllowed(true)
.setReminderRequired(true)
.build());
}
@Test
public void test_size()
{
assertThat(habitList.size(), equalTo(10));
}
@Test
public void test_countActive()
{
assertThat(activeHabits.size(), equalTo(6));
}
@Test
public void test_getByPosition()
{
assertThat(habitList.getByPosition(0), equalTo(habitsArray.get(0)));
assertThat(habitList.getByPosition(3), equalTo(habitsArray.get(3)));
assertThat(habitList.getByPosition(9), equalTo(habitsArray.get(9)));
assertThat(activeHabits.getByPosition(0), equalTo(habitsArray.get(2)));
}
@Test
public void test_getHabitsWithReminder()
{
assertThat(reminderHabits.size(), equalTo(4));
assertThat(reminderHabits.getByPosition(1),
equalTo(habitsArray.get(3)));
}
@Test
public void test_get_withInvalidId()
{
assertThat(habitList.getById(100L), is(nullValue()));
}
@Test
public void test_get_withValidId()
{
Habit habit1 = habitsArray.get(0);
Habit habit2 = habitList.getById(habit1.getId());
assertThat(habit1, equalTo(habit2));
}
@Test
public void test_reorder()
{
int operations[][] = {
{ 5, 2 }, { 3, 7 }, { 4, 4 }, { 3, 2 }
};
int expectedPosition[][] = {
{ 0, 1, 3, 4, 5, 2, 6, 7, 8, 9 },
{ 0, 1, 7, 3, 4, 2, 5, 6, 8, 9 },
{ 0, 1, 7, 3, 4, 2, 5, 6, 8, 9 },
{ 0, 1, 7, 2, 4, 3, 5, 6, 8, 9 },
};
for (int i = 0; i < operations.length; i++)
{
int from = operations[i][0];
int to = operations[i][1];
Habit fromHabit = habitList.getByPosition(from);
Habit toHabit = habitList.getByPosition(to);
habitList.reorder(fromHabit, toHabit);
int actualPositions[] = new int[10];
for (int j = 0; j < 10; j++)
{
Habit h = habitList.getById(j);
assertNotNull(h);
actualPositions[j] = habitList.indexOf(h);
}
assertThat(actualPositions, equalTo(expectedPosition[i]));
}
}
@Test
public void test_writeCSV() throws IOException
{
habitList.removeAll();
Habit h1 = fixtures.createEmptyHabit();
h1.setName("Meditate");
h1.setDescription("Did you meditate this morning?");
h1.setFrequency(Frequency.DAILY);
h1.setColor(3);
Habit h2 = fixtures.createEmptyHabit();
h2.setName("Wake up early");
h2.setDescription("Did you wake up before 6am?");
h2.setFrequency(new Frequency(2, 3));
h2.setColor(5);
habitList.update(h1);
habitList.update(h2);
String expectedCSV =
"Position,Name,Description,NumRepetitions,Interval,Color\n" +
"001,Meditate,Did you meditate this morning?,1,1,#AFB42B\n" +
"002,Wake up early,Did you wake up before 6am?,2,3,#00897B\n";
StringWriter writer = new StringWriter();
habitList.writeCSV(writer);
MatcherAssert.assertThat(writer.toString(), equalTo(expectedCSV));
}
@Test
public void test_ordering()
{
habitList.removeAll();
Habit h3 = fixtures.createEmptyHabit();
h3.setName("C Habit");
h3.setColor(0);
habitList.update(h3);
Habit h1 = fixtures.createEmptyHabit();
h1.setName("A Habit");
h1.setColor(2);
habitList.update(h1);
Habit h4 = fixtures.createEmptyHabit();
h4.setName("D Habit");
h4.setColor(1);
habitList.update(h4);
Habit h2 = fixtures.createEmptyHabit();
h2.setName("B Habit");
h2.setColor(2);
habitList.update(h2);
habitList.setOrder(BY_POSITION);
assertThat(habitList.getByPosition(0), equalTo(h3));
assertThat(habitList.getByPosition(1), equalTo(h1));
assertThat(habitList.getByPosition(2), equalTo(h4));
assertThat(habitList.getByPosition(3), equalTo(h2));
habitList.setOrder(BY_NAME);
assertThat(habitList.getByPosition(0), equalTo(h1));
assertThat(habitList.getByPosition(1), equalTo(h2));
assertThat(habitList.getByPosition(2), equalTo(h3));
assertThat(habitList.getByPosition(3), equalTo(h4));
habitList.remove(h1);
habitList.add(h1);
assertThat(habitList.getByPosition(0), equalTo(h1));
habitList.setOrder(BY_COLOR);
assertThat(habitList.getByPosition(0), equalTo(h3));
assertThat(habitList.getByPosition(1), equalTo(h4));
assertThat(habitList.getByPosition(2), equalTo(h1));
assertThat(habitList.getByPosition(3), equalTo(h2));
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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 org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.junit.*;
import org.junit.runner.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.core.IsEqual.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class HabitRecordTest extends BaseAndroidTest
{
@Override
public void setUp()
{
super.setUp();
Habit h = component.getModelFactory().buildHabit();
h.setName("Hello world");
h.setId(1000L);
HabitRecord record = new HabitRecord();
record.copyFrom(h);
record.position = 0;
record.save(1000L);
}
@Test
public void testCopyFrom()
{
Habit habit = component.getModelFactory().buildHabit();
habit.setName("Hello world");
habit.setDescription("Did you greet the world today?");
habit.setColor(1);
habit.setArchived(true);
habit.setFrequency(Frequency.THREE_TIMES_PER_WEEK);
habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
habit.setId(1000L);
HabitRecord rec = new HabitRecord();
rec.copyFrom(habit);
assertThat(rec.name, equalTo(habit.getName()));
assertThat(rec.description, equalTo(habit.getDescription()));
assertThat(rec.color, equalTo(habit.getColor()));
assertThat(rec.archived, equalTo(1));
assertThat(rec.freqDen, equalTo(7));
assertThat(rec.freqNum, equalTo(3));
Reminder reminder = habit.getReminder();
assertThat(rec.reminderDays, equalTo(reminder.getDays().toInteger()));
assertThat(rec.reminderHour, equalTo(reminder.getHour()));
assertThat(rec.reminderMin, equalTo(reminder.getMinute()));
habit.setReminder(null);
rec.copyFrom(habit);
assertThat(rec.reminderMin, equalTo(null));
assertThat(rec.reminderHour, equalTo(null));
assertThat(rec.reminderDays, equalTo(0));
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.util.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
@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();
}
}

View File

@@ -0,0 +1,177 @@
/*
* 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.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.junit.*;
import org.junit.rules.*;
import org.junit.runner.*;
import java.util.*;
import static junit.framework.Assert.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.core.IsEqual.*;
@SuppressWarnings("JavaDoc")
@RunWith(AndroidJUnit4.class)
@MediumTest
public class SQLiteHabitListTest extends BaseAndroidTest
{
@Rule
public ExpectedException exception = ExpectedException.none();
private SQLiteHabitList habitList;
private ModelFactory modelFactory;
@Override
public void setUp()
{
super.setUp();
this.habitList = (SQLiteHabitList) super.habitList;
fixtures.purgeHabits(habitList);
modelFactory = component.getModelFactory();
for (int i = 0; i < 10; i++)
{
Habit h = modelFactory.buildHabit();
h.setName("habit " + i);
h.setId((long) i);
if (i % 2 == 0) h.setArchived(true);
HabitRecord record = new HabitRecord();
record.copyFrom(h);
record.position = i;
record.save(i);
}
}
@Test
public void testAdd_withDuplicate()
{
Habit habit = modelFactory.buildHabit();
habitList.add(habit);
exception.expect(IllegalArgumentException.class);
habitList.add(habit);
}
@Test
public void testAdd_withId()
{
Habit habit = modelFactory.buildHabit();
habit.setName("Hello world with id");
habit.setId(12300L);
habitList.add(habit);
assertThat(habit.getId(), equalTo(12300L));
HabitRecord record = getRecord(12300L);
assertNotNull(record);
assertThat(record.name, equalTo(habit.getName()));
}
@Test
public void testAdd_withoutId()
{
Habit habit = modelFactory.buildHabit();
habit.setName("Hello world");
assertNull(habit.getId());
habitList.add(habit);
assertNotNull(habit.getId());
HabitRecord record = getRecord(habit.getId());
assertNotNull(record);
assertThat(record.name, equalTo(habit.getName()));
}
@Test
public void testSize()
{
assertThat(habitList.size(), equalTo(10));
}
@Test
public void testGetAll_withArchived()
{
List<Habit> habits = habitList.toList();
assertThat(habits.size(), equalTo(10));
assertThat(habits.get(3).getName(), equalTo("habit 3"));
}
@Test
public void testGetById()
{
Habit h1 = habitList.getById(0);
assertNotNull(h1);
assertThat(h1.getName(), equalTo("habit 0"));
Habit h2 = habitList.getById(0);
assertNotNull(h2);
assertThat(h1, equalTo(h2));
}
@Test
public void testGetById_withInvalid()
{
long invalidId = 9183792001L;
Habit h1 = habitList.getById(invalidId);
assertNull(h1);
}
@Test
public void testGetByPosition()
{
Habit h = habitList.getByPosition(5);
assertNotNull(h);
assertThat(h.getName(), equalTo("habit 5"));
}
@Test
public void testIndexOf()
{
Habit h1 = habitList.getByPosition(5);
assertNotNull(h1);
assertThat(habitList.indexOf(h1), equalTo(5));
Habit h2 = modelFactory.buildHabit();
assertThat(habitList.indexOf(h2), equalTo(-1));
h2.setId(1000L);
assertThat(habitList.indexOf(h2), equalTo(-1));
}
private HabitRecord getRecord(long id)
{
return new Select()
.from(HabitRecord.class)
.where("id = ?", id)
.executeSingle();
}
}

View File

@@ -0,0 +1,143 @@
/*
* 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.annotation.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import com.activeandroid.query.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.util.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.IsNot.not;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class SQLiteRepetitionListTest extends BaseAndroidTest
{
private Habit habit;
private long today;
private RepetitionList repetitions;
private long day;
@Override
public void setUp()
{
super.setUp();
habit = fixtures.createLongHabit();
repetitions = habit.getRepetitions();
today = DateUtils.getStartOfToday();
day = DateUtils.millisecondsInOneDay;
}
@Test
public void testAdd()
{
RepetitionRecord record = getByTimestamp(today + day);
assertThat(record, is(nullValue()));
Repetition rep = new Repetition(today + day);
habit.getRepetitions().add(rep);
record = getByTimestamp(today + day);
assertThat(record, is(not(nullValue())));
}
@Test
public void testGetByInterval()
{
List<Repetition> reps =
repetitions.getByInterval(today - 10 * day, today);
assertThat(reps.size(), equalTo(8));
assertThat(reps.get(0).getTimestamp(), equalTo(today - 10 * day));
assertThat(reps.get(4).getTimestamp(), equalTo(today - 5 * day));
assertThat(reps.get(5).getTimestamp(), equalTo(today - 3 * day));
}
@Test
public void testGetByTimestamp()
{
Repetition rep = repetitions.getByTimestamp(today);
assertThat(rep, is(not(nullValue())));
assertThat(rep.getTimestamp(), equalTo(today));
rep = repetitions.getByTimestamp(today - 2 * day);
assertThat(rep, is(nullValue()));
}
@Test
public void testGetOldest()
{
Repetition rep = repetitions.getOldest();
assertThat(rep, is(not(nullValue())));
assertThat(rep.getTimestamp(), equalTo(today - 120 * day));
}
@Test
public void testGetOldest_withEmptyHabit()
{
Habit empty = fixtures.createEmptyHabit();
Repetition rep = empty.getRepetitions().getOldest();
assertThat(rep, is(nullValue()));
}
@Test
public void testRemove()
{
RepetitionRecord record = getByTimestamp(today);
assertThat(record, is(not(nullValue())));
Repetition rep = record.toRepetition();
repetitions.remove(rep);
record = getByTimestamp(today);
assertThat(record, is(nullValue()));
}
@Nullable
private RepetitionRecord getByTimestamp(long timestamp)
{
return selectByTimestamp(timestamp).executeSingle();
}
@NonNull
private From selectByTimestamp(long timestamp)
{
return new Select()
.from(RepetitionRecord.class)
.where("habit = ?", habit.getId())
.and("timestamp = ?", timestamp);
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.util.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
@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();
}
}

View File

@@ -0,0 +1,173 @@
/*
* 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.pebble;
import android.content.*;
import android.support.annotation.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import com.getpebble.android.kit.*;
import com.getpebble.android.kit.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.json.*;
import org.junit.*;
import org.junit.runner.*;
import static com.getpebble.android.kit.Constants.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.core.IsEqual.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class PebbleReceiverTest extends BaseAndroidTest
{
private Habit habit1;
private Habit habit2;
@Override
public void setUp()
{
super.setUp();
fixtures.purgeHabits(habitList);
habit1 = fixtures.createEmptyHabit();
habit1.setName("Exercise");
habit2 = fixtures.createEmptyHabit();
habit2.setName("Meditate");
}
@Test
public void testCount() throws Exception
{
onPebbleReceived((dict) -> {
assertThat(dict.getString(0), equalTo("COUNT"));
assertThat(dict.getInteger(1), equalTo(2L));
});
PebbleDictionary dict = buildCountRequest();
sendFromPebbleToAndroid(dict);
awaitLatch();
}
@Test
public void testFetch() throws Exception
{
onPebbleReceived((dict) -> {
assertThat(dict.getString(0), equalTo("HABIT"));
assertThat(dict.getInteger(1), equalTo(habit2.getId()));
assertThat(dict.getString(2), equalTo(habit2.getName()));
assertThat(dict.getInteger(3), equalTo(0L));
});
PebbleDictionary dict = buildFetchRequest(1);
sendFromPebbleToAndroid(dict);
awaitLatch();
}
// @Test
// public void testToggle() throws Exception
// {
// int v = habit1.getCheckmarks().getTodayValue();
// assertThat(v, equalTo(Checkmark.UNCHECKED));
//
// onPebbleReceived((dict) -> {
// assertThat(dict.getString(0), equalTo("OK"));
// int value = habit1.getCheckmarks().getTodayValue();
// assertThat(value, equalTo(200)); //Checkmark.CHECKED_EXPLICITLY));
// });
//
// PebbleDictionary dict = buildToggleRequest(habit1.getId());
// sendFromPebbleToAndroid(dict);
// awaitLatch();
// }
@NonNull
protected PebbleDictionary buildCountRequest()
{
PebbleDictionary dict = new PebbleDictionary();
dict.addString(0, "COUNT");
return dict;
}
@NonNull
protected PebbleDictionary buildFetchRequest(int position)
{
PebbleDictionary dict = new PebbleDictionary();
dict.addString(0, "FETCH");
dict.addInt32(1, position);
return dict;
}
protected void onPebbleReceived(PebbleProcessor processor)
{
BroadcastReceiver pebbleReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
try
{
String jsonData = intent.getStringExtra(MSG_DATA);
PebbleDictionary dict = PebbleDictionary.fromJson(jsonData);
processor.process(dict);
latch.countDown();
targetContext.unregisterReceiver(this);
}
catch (JSONException e)
{
throw new RuntimeException(e);
}
}
};
IntentFilter filter = new IntentFilter(Constants.INTENT_APP_SEND);
targetContext.registerReceiver(pebbleReceiver, filter);
}
protected void sendFromPebbleToAndroid(PebbleDictionary dict)
{
Intent intent = new Intent(Constants.INTENT_APP_RECEIVE);
intent.putExtra(Constants.APP_UUID, PebbleReceiver.WATCHAPP_UUID);
intent.putExtra(Constants.TRANSACTION_ID, 0);
intent.putExtra(Constants.MSG_DATA, dict.toJsonString());
targetContext.sendBroadcast(intent);
}
private PebbleDictionary buildToggleRequest(long habitId)
{
PebbleDictionary dict = new PebbleDictionary();
dict.addString(0, "TOGGLE");
dict.addInt32(1, (int) habitId);
return dict;
}
interface PebbleProcessor
{
void process(PebbleDictionary dict);
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.tasks;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.*;
import java.util.*;
import static junit.framework.Assert.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.IsNot.not;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class ExportCSVTaskTest extends BaseAndroidTest
{
@Before
@Override
public void setUp()
{
super.setUp();
}
@Test
public void testExportCSV() throws Throwable
{
fixtures.purgeHabits(habitList);
fixtures.createShortHabit();
List<Habit> selected = new LinkedList<>();
for (Habit h : habitList) selected.add(h);
taskRunner.execute(
new ExportCSVTask(targetContext,habitList, selected, archiveFilename -> {
assertThat(archiveFilename, is(not(nullValue())));
File f = new File(archiveFilename);
assertTrue(f.exists());
assertTrue(f.canRead());
}));
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.tasks;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.*;
import static junit.framework.Assert.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.IsNot.not;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class ExportDBTaskTest extends BaseAndroidTest
{
@Before
public void setUp()
{
super.setUp();
}
@Test
public void testExportCSV() throws Throwable
{
ExportDBTask task = new ExportDBTask(targetContext, filename -> {
assertThat(filename, is(not(nullValue())));
File f = new File(filename);
assertTrue(f.exists());
assertTrue(f.canRead());
});
taskRunner.execute(task);
}
}

View File

@@ -1,225 +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.ui;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.contrib.RecyclerViewActions;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.longClick;
import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup;
import static android.support.test.espresso.matcher.ViewMatchers.Visibility.VISIBLE;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.isoron.uhabits.ui.HabitMatchers.containsHabit;
import static org.isoron.uhabits.ui.HabitMatchers.withName;
public class MainActivityActions
{
public static String addHabit()
{
return addHabit(false);
}
public static String addHabit(boolean openDialogs)
{
String name = "New Habit " + new Random().nextInt(1000000);
String description = "Did you perform your new habit today?";
String num = "4";
String den = "8";
onView(withId(R.id.action_add))
.perform(click());
typeHabitData(name, description, num, den);
if(openDialogs)
{
onView(withId(R.id.buttonPickColor))
.perform(click());
pressBack();
onView(withId(R.id.inputReminderTime))
.perform(click());
onView(withText("Done"))
.perform(click());
onView(withId(R.id.inputReminderDays))
.perform(click());
onView(withText("OK"))
.perform(click());
}
onView(withId(R.id.buttonSave))
.perform(click());
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label));
return name;
}
public static void typeHabitData(String name, String description, String num, String den)
{
onView(withId(R.id.input_name))
.perform(replaceText(name));
onView(withId(R.id.input_description))
.perform(replaceText(description));
try
{
onView(allOf(withId(R.id.sFrequency), withEffectiveVisibility(VISIBLE)))
.perform(click());
onData(allOf(instanceOf(String.class), startsWith("Custom")))
.inRoot(isPlatformPopup())
.perform(click());
}
catch(NoMatchingViewException e)
{
// ignored
}
onView(withId(R.id.input_freq_num))
.perform(replaceText(num));
onView(withId(R.id.input_freq_den))
.perform(replaceText(den));
}
public static void selectHabit(String name)
{
selectHabits(Collections.singletonList(name));
}
public static void selectHabits(List<String> names)
{
boolean first = true;
for(String name : names)
{
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label))
.perform(first ? longClick() : click());
first = false;
}
}
public static void assertHabitsDontExist(List<String> names)
{
for(String name : names)
onView(withId(R.id.listView))
.check(matches(not(containsHabit(withName(name)))));
}
public static void assertHabitExists(String name)
{
List<String> names = new LinkedList<>();
names.add(name);
assertHabitsExist(names);
}
public static void assertHabitsExist(List<String> names)
{
for(String name : names)
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.check(matches(isDisplayed()));
}
public static void deleteHabit(String name)
{
deleteHabits(Collections.singletonList(name));
}
public static void deleteHabits(List<String> names)
{
selectHabits(names);
clickMenuItem(R.string.delete);
onView(withText("OK"))
.perform(click());
assertHabitsDontExist(names);
}
public static void clickMenuItem(int stringId)
{
try
{
onView(withText(stringId)).perform(click());
}
catch (Exception e1)
{
try
{
onView(withContentDescription(stringId)).perform(click());
}
catch(Exception e2)
{
clickHiddenMenuItem(stringId);
}
}
}
private static void clickHiddenMenuItem(int stringId)
{
try
{
// Try the ActionMode overflow menu first
onView(allOf(withContentDescription("More options"), withParent(withParent(
withClassName(containsString("Action")))))).perform(click());
}
catch (Exception e1)
{
// Try the toolbar overflow menu
onView(allOf(withContentDescription("More options"), withParent(withParent(
withClassName(containsString("Toolbar")))))).perform(click());
}
onView(withText(stringId)).perform(click());
}
public static void clickSettingsItem(String text)
{
onView(withClassName(containsString("RecyclerView")))
.perform(RecyclerViewActions.actionOnItem(
hasDescendant(withText(containsString(text))),
click()));
}
}

View File

@@ -1,346 +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.ui;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.isoron.uhabits.MainActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.longClick;
import static android.support.test.espresso.action.ViewActions.scrollTo;
import static android.support.test.espresso.action.ViewActions.swipeLeft;
import static android.support.test.espresso.action.ViewActions.swipeRight;
import static android.support.test.espresso.action.ViewActions.swipeUp;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.Intents.intending;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.isoron.uhabits.ui.HabitMatchers.withName;
import static org.isoron.uhabits.ui.HabitViewActions.clickAtRandomLocations;
import static org.isoron.uhabits.ui.HabitViewActions.toggleAllCheckmarks;
import static org.isoron.uhabits.ui.MainActivityActions.addHabit;
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitExists;
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsDontExist;
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsExist;
import static org.isoron.uhabits.ui.MainActivityActions.clickMenuItem;
import static org.isoron.uhabits.ui.MainActivityActions.clickSettingsItem;
import static org.isoron.uhabits.ui.MainActivityActions.deleteHabit;
import static org.isoron.uhabits.ui.MainActivityActions.deleteHabits;
import static org.isoron.uhabits.ui.MainActivityActions.selectHabit;
import static org.isoron.uhabits.ui.MainActivityActions.selectHabits;
import static org.isoron.uhabits.ui.MainActivityActions.typeHabitData;
import static org.isoron.uhabits.ui.ShowHabitActivityActions.openHistoryEditor;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainTest
{
private SystemHelper sys;
@Rule
public IntentsTestRule<MainActivity> activityRule = new IntentsTestRule<>(
MainActivity.class);
private Context targetContext;
@Before
public void setup()
{
Context context = InstrumentationRegistry.getInstrumentation().getContext();
sys = new SystemHelper(context);
sys.disableAllAnimations();
sys.acquireWakeLock();
sys.unlockScreen();
targetContext = InstrumentationRegistry.getTargetContext();
Instrumentation.ActivityResult okResult = new Instrumentation.ActivityResult(
Activity.RESULT_OK, new Intent());
intending(hasAction(equalTo(Intent.ACTION_SEND))).respondWith(okResult);
intending(hasAction(equalTo(Intent.ACTION_SENDTO))).respondWith(okResult);
intending(hasAction(equalTo(Intent.ACTION_VIEW))).respondWith(okResult);
skipTutorial();
}
@After
public void tearDown()
{
sys.releaseWakeLock();
}
public void skipTutorial()
{
try
{
for (int i = 0; i < 10; i++)
onView(allOf(withClassName(endsWith("AppCompatImageButton")),
isDisplayed())).perform(click());
}
catch (NoMatchingViewException e)
{
// ignored
}
}
/**
* User opens the app, creates some habits, selects them, archives them, select 'show archived'
* on the menu, selects the previously archived habits and then deletes them.
*/
@Test
public void testArchiveHabits()
{
List<String> names = new LinkedList<>();
for(int i = 0; i < 3; i++)
names.add(addHabit());
selectHabits(names);
clickMenuItem(R.string.archive);
assertHabitsDontExist(names);
clickMenuItem(R.string.show_archived);
assertHabitsExist(names);
selectHabits(names);
clickMenuItem(R.string.unarchive);
clickMenuItem(R.string.show_archived);
assertHabitsExist(names);
deleteHabits(names);
}
/**
* User opens the app, clicks the add button, types some bogus information, tries to save,
* dialog displays an error.
*/
@Test
public void testAddInvalidHabit()
{
onView(withId(R.id.action_add))
.perform(click());
typeHabitData("", "", "15", "7");
onView(withId(R.id.buttonSave)).perform(click());
onView(withId(R.id.input_name)).check(matches(isDisplayed()));
}
/**
* User creates a habit, toggles a bunch of checkmarks, clicks the habit to open the statistics
* screen, scrolls down to some views, then scrolls the views backwards and forwards in time.
*/
@Test
public void testAddHabitAndViewStats() throws InterruptedException
{
String name = addHabit(true);
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.llButtons))
.perform(toggleAllCheckmarks());
Thread.sleep(1200);
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label))
.perform(click());
onView(withId(R.id.scoreView))
.perform(scrollTo(), swipeRight());
onView(withId(R.id.punchcardView))
.perform(scrollTo(), swipeRight());
}
/**
* User creates a habit, selects the habit, clicks edit button, changes some information about
* the habit, click save button, sees changes on the main window, selects habit again,
* changes color, then deletes the habit.
*/
@Test
public void testEditHabit()
{
String name = addHabit();
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label))
.perform(longClick());
clickMenuItem(R.string.edit);
String modifiedName = "Modified " + new Random().nextInt(10000);
typeHabitData(modifiedName, "", "1", "1");
onView(withId(R.id.buttonSave))
.perform(click());
assertHabitExists(modifiedName);
selectHabit(modifiedName);
clickMenuItem(R.string.color_picker_default_title);
pressBack();
deleteHabit(modifiedName);
}
/**
* User creates a habit, opens statistics page, clicks button to edit history, adds some
* checkmarks, closes dialog, sees the modified history calendar.
*/
@Test
public void testEditHistory()
{
String name = addHabit();
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label))
.perform(click());
openHistoryEditor();
onView(withClassName(endsWith("HabitHistoryView")))
.perform(clickAtRandomLocations(20));
pressBack();
onView(withId(R.id.historyView))
.perform(scrollTo(), swipeRight(), swipeLeft());
}
/**
* User opens menu, clicks settings, sees settings screen.
*/
@Test
public void testSettings()
{
clickMenuItem(R.string.settings);
}
/**
* User opens menu, clicks about, sees about screen.
*/
@Test
public void testAbout()
{
clickMenuItem(R.string.about);
onView(isRoot()).perform(swipeUp());
}
/**
* User opens menu, clicks Help, sees website.
*/
@Test
public void testHelp()
{
clickMenuItem(R.string.help);
intended(hasAction(Intent.ACTION_VIEW));
}
/**
* User creates a habit, exports full backup, deletes the habit, restores backup, sees that the
* previously created habit has appeared back.
*/
@Test
public void testExportImportDB()
{
String name = addHabit();
clickMenuItem(R.string.settings);
String date = DateHelper.getBackupDateFormat().format(DateHelper.getLocalTime());
date = date.substring(0, date.length() - 2);
clickSettingsItem("Export full backup");
intended(hasAction(Intent.ACTION_SEND));
deleteHabit(name);
clickMenuItem(R.string.settings);
clickSettingsItem("Import data");
onData(allOf(is(instanceOf(String.class)), startsWith("Backups")))
.perform(click());
onData(allOf(is(instanceOf(String.class)), containsString(date)))
.perform(click());
selectHabit(name);
}
/**
* User creates a habit, opens settings, clicks export as CSV, is asked what activity should
* handle the file.
*/
@Test
public void testExportCSV()
{
addHabit();
clickMenuItem(R.string.settings);
clickSettingsItem("Export as CSV");
intended(hasAction(Intent.ACTION_SEND));
}
/**
* User opens the settings and generates a bug report.
*/
@Test
public void testGenerateBugReport()
{
clickMenuItem(R.string.settings);
clickSettingsItem("Generate bug report");
intended(hasAction(Intent.ACTION_SENDTO));
}
}

View File

@@ -1,168 +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.unit;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.tasks.BaseTask;
import org.isoron.uhabits.tasks.ExportDBTask;
import org.isoron.uhabits.tasks.ImportDataTask;
import java.io.File;
import java.io.InputStream;
import java.util.Random;
import static org.junit.Assert.fail;
public class HabitFixtures
{
public static boolean NON_DAILY_HABIT_CHECKS[] = { true, false, false, true, true, true, false,
false, true, true };
public static Habit createShortHabit()
{
Habit habit = new Habit();
habit.name = "Wake up early";
habit.description = "Did you wake up before 6am?";
habit.freqNum = 2;
habit.freqDen = 3;
habit.save();
long timestamp = DateHelper.getStartOfToday();
for(boolean c : NON_DAILY_HABIT_CHECKS)
{
if(c) habit.repetitions.toggle(timestamp);
timestamp -= DateHelper.millisecondsInOneDay;
}
return habit;
}
public static Habit createEmptyHabit()
{
Habit habit = new Habit();
habit.name = "Meditate";
habit.description = "Did you meditate this morning?";
habit.color = 3;
habit.freqNum = 1;
habit.freqDen = 1;
habit.save();
return habit;
}
public static Habit createLongHabit()
{
Habit habit = createEmptyHabit();
habit.freqNum = 3;
habit.freqDen = 7;
habit.color = 4;
habit.save();
long day = DateHelper.millisecondsInOneDay;
long today = DateHelper.getStartOfToday();
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, 28, 50, 51, 52,
53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, 81, 83, 89, 90, 91, 95,
102, 103, 108, 109, 120};
for(int mark : marks)
habit.repetitions.toggle(today - mark * day);
return habit;
}
public static void generateHugeDataSet() throws Throwable
{
final int nHabits = 30;
final int nYears = 5;
DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command()
{
@Override
public void execute()
{
Random rand = new Random();
for(int i = 0; i < nHabits; i++)
{
Log.i("HabitFixture", String.format("Creating habit %d / %d", i, nHabits));
Habit habit = new Habit();
habit.name = String.format("Habit %d", i);
habit.save();
long today = DateHelper.getStartOfToday();
long day = DateHelper.millisecondsInOneDay;
for(int j = 0; j < 365 * nYears; j++)
{
if(rand.nextBoolean())
habit.repetitions.toggle(today - j * day);
}
habit.scores.getTodayValue();
habit.streaks.getAll(1);
}
}
});
ExportDBTask task = new ExportDBTask(null);
task.setListener(new ExportDBTask.Listener()
{
@Override
public void onExportDBFinished(@Nullable String filename)
{
if(filename != null)
Log.i("HabitFixture", String.format("Huge data set exported to %s", filename));
else
Log.i("HabitFixture", "Failed to save database");
}
});
task.execute();
BaseTask.waitForTasks(30000);
}
public static void loadHugeDataSet(Context testContext) throws Throwable
{
File baseDir = DatabaseHelper.getFilesDir("Backups");
if(baseDir == null) fail("baseDir should not be null");
File dst = new File(String.format("%s/%s", baseDir.getPath(), "loopHuge.db"));
InputStream in = testContext.getAssets().open("fixtures/loopHuge.db");
DatabaseHelper.copy(in, dst);
ImportDataTask task = new ImportDataTask(dst, null);
task.execute();
BaseTask.waitForTasks(30000);
}
public static void purgeHabits()
{
for(Habit h : Habit.getAll(true))
h.cascadeDelete();
}
}

View File

@@ -1,85 +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.unit.commands;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.commands.CreateHabitCommand;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class CreateHabitCommandTest extends BaseTest
{
private CreateHabitCommand command;
private Habit model;
@Before
public void setup()
{
super.setup();
model = new Habit();
model.name = "New habit";
command = new CreateHabitCommand(model);
HabitFixtures.purgeHabits();
}
@Test
public void testExecuteUndoRedo()
{
assertTrue(Habit.getAll(true).isEmpty());
command.execute();
List<Habit> allHabits = Habit.getAll(true);
assertThat(allHabits.size(), equalTo(1));
Habit habit = allHabits.get(0);
Long id = habit.getId();
assertThat(habit.name, equalTo(model.name));
command.undo();
assertTrue(Habit.getAll(true).isEmpty());
command.execute();
allHabits = Habit.getAll(true);
assertThat(allHabits.size(), equalTo(1));
habit = allHabits.get(0);
Long newId = habit.getId();
assertThat(id, equalTo(newId));
assertThat(habit.name, equalTo(model.name));
}
}

View File

@@ -1,85 +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.unit.commands;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.commands.DeleteHabitsCommand;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import java.util.LinkedList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DeleteHabitsCommandTest extends BaseTest
{
private DeleteHabitsCommand command;
private LinkedList<Habit> habits;
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
habits = new LinkedList<>();
// Habits that shuold be deleted
for(int i = 0; i < 3; i ++)
{
Habit habit = HabitFixtures.createShortHabit();
habits.add(habit);
}
// Extra habit that should not be deleted
Habit extraHabit = HabitFixtures.createShortHabit();
extraHabit.name = "extra";
extraHabit.save();
command = new DeleteHabitsCommand(habits);
}
@Test
public void testExecuteUndoRedo()
{
assertThat(Habit.getAll(true).size(), equalTo(4));
command.execute();
assertThat(Habit.getAll(true).size(), equalTo(1));
assertThat(Habit.getAll(true).get(0).name, equalTo("extra"));
thrown.expect(UnsupportedOperationException.class);
command.undo();
}
}

Some files were not shown because too many files have changed in this diff Show More