Feature #1756 : Bookmark System (#1935)

* Add bookmark star images

* Add bookmark item in navigation menu

* Add Activity for bookmarks

* Implement bookmarks viewpager

* Bookmark object and bookmarkDao

* Implement Bookmark Picture Fragment and Controller

* Implement image detail bookmark menu action UI

* contentProvider + config + dao rework

* Fix Dao and Content Provider crashes

* Link bookmark controllers and dao

* Implement bookmark location fragment, controller

* Add bookmark icon to location items

* Add empty bookmark list behavior and refactoring

* bookmarkLocation dao and contentProvider

* Fix bookmarks location crashes

* Rename and refactor classes

* Implement location list refresh

* Fix picture list update
When user come back from detail picture fragment, it solve the refresh bug.

* full test coverage

* Refactor bookmarks classes

* Fix bookmarks pictures loading

* Fix bookmark locations list display

* Java Documetation

* Fix Code review quality

* Fix DB version update

* Remove forgotten todo

* Update bookmark activity base class
Change from AuthenticatedActivity to BaseNavigationActivity
This commit is contained in:
Victor-Bonin 2018-10-25 17:54:22 +02:00 committed by Josephine Lim
parent 89d2d0cfe0
commit 80a9c94653
42 changed files with 2361 additions and 12 deletions

View file

@ -0,0 +1,262 @@
package fr.free.nrw.commons.bookmarks.locations
import android.content.ContentProviderClient
import android.content.ContentValues
import android.database.Cursor
import android.database.MatrixCursor
import android.database.sqlite.SQLiteDatabase
import android.net.Uri
import android.os.RemoteException
import com.nhaarman.mockito_kotlin.*
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider.BASE_URI
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.*
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.Sitelinks
import junit.framework.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class)
class BookMarkLocationDaoTest {
private val columns = arrayOf(COLUMN_NAME,
COLUMN_DESCRIPTION,
COLUMN_CATEGORY,
COLUMN_LABEL_TEXT,
COLUMN_LABEL_ICON,
COLUMN_IMAGE_URL,
COLUMN_WIKIPEDIA_LINK,
COLUMN_WIKIDATA_LINK,
COLUMN_COMMONS_LINK,
COLUMN_LAT,
COLUMN_LONG)
private val client: ContentProviderClient = mock()
private val database: SQLiteDatabase = mock()
private val captor = argumentCaptor<ContentValues>()
private lateinit var testObject: BookmarkLocationsDao
private lateinit var examplePlaceBookmark: Place
private lateinit var exampleLabel: Place.Label
private lateinit var exampleUri: Uri
private lateinit var exampleLocation: LatLng
private lateinit var builder: Sitelinks.Builder
@Before
fun setUp() {
exampleLabel = Place.Label.FOREST
exampleUri = Uri.parse("wikimedia/uri")
exampleLocation = LatLng(40.0,51.4, 1f)
builder = Sitelinks.Builder()
builder.setWikipediaLink("wikipediaLink")
builder.setWikidataLink("wikidataLink")
builder.setCommonsLink("commonsLink")
examplePlaceBookmark = Place("placeName", exampleLabel, "placeDescription",
exampleUri, exampleLocation, "placeCategory", builder.build())
testObject = BookmarkLocationsDao { client }
}
@Test
fun createTable() {
onCreate(database)
verify(database).execSQL(CREATE_TABLE_STATEMENT)
}
@Test
fun deleteTable() {
onDelete(database)
inOrder(database) {
verify(database).execSQL(DROP_TABLE_STATEMENT)
verify(database).execSQL(CREATE_TABLE_STATEMENT)
}
}
@Test
fun createFromCursor() {
createCursor(1).let { cursor ->
cursor.moveToFirst()
testObject.fromCursor(cursor).let {
assertEquals("placeName", it.name)
assertEquals(Place.Label.FOREST, it.label)
assertEquals("placeDescription", it.longDescription)
assertEquals(exampleUri, it.secondaryImageUrl)
assertEquals(40.0, it.location.latitude)
assertEquals(51.4, it.location.longitude)
assertEquals("placeCategory", it.category)
assertEquals(builder.build().wikipediaLink, it.siteLinks.wikipediaLink)
assertEquals(builder.build().wikidataLink, it.siteLinks.wikidataLink)
assertEquals(builder.build().commonsLink, it.siteLinks.commonsLink)
}
}
}
@Test
fun getAllLocationBookmarks() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(14))
var result = testObject.allBookmarksLocations
assertEquals(14,(result.size))
}
@Test(expected = RuntimeException::class)
fun getAllLocationBookmarksTranslatesExceptions() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException(""))
testObject.allBookmarksLocations
}
@Test
fun getAllLocationBookmarksReturnsEmptyList_emptyCursor() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(0))
assertTrue(testObject.allBookmarksLocations.isEmpty())
}
@Test
fun getAllLocationBookmarksReturnsEmptyList_nullCursor() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null)
assertTrue(testObject.allBookmarksLocations.isEmpty())
}
@Test
fun cursorsAreClosedAfterGetAllLocationBookmarksQuery() {
val mockCursor: Cursor = mock()
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor)
whenever(mockCursor.moveToFirst()).thenReturn(false)
testObject.allBookmarksLocations
verify(mockCursor).close()
}
@Test
fun updateNewLocationBookmark() {
whenever(client.insert(any(), any())).thenReturn(Uri.EMPTY)
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(null)
assertTrue(testObject.updateBookmarkLocation(examplePlaceBookmark))
verify(client).insert(eq(BASE_URI), captor.capture())
captor.firstValue.let { cv ->
assertEquals(11, cv.size())
assertEquals(examplePlaceBookmark.name, cv.getAsString(COLUMN_NAME))
assertEquals(examplePlaceBookmark.longDescription, cv.getAsString(COLUMN_DESCRIPTION))
assertEquals(examplePlaceBookmark.label.text, cv.getAsString(COLUMN_LABEL_TEXT))
assertEquals(examplePlaceBookmark.category, cv.getAsString(COLUMN_CATEGORY))
assertEquals(examplePlaceBookmark.location.latitude, cv.getAsDouble(COLUMN_LAT))
assertEquals(examplePlaceBookmark.location.longitude, cv.getAsDouble(COLUMN_LONG))
assertEquals(examplePlaceBookmark.siteLinks.wikipediaLink.toString(), cv.getAsString(COLUMN_WIKIPEDIA_LINK))
assertEquals(examplePlaceBookmark.siteLinks.wikidataLink.toString(), cv.getAsString(COLUMN_WIKIDATA_LINK))
assertEquals(examplePlaceBookmark.siteLinks.commonsLink.toString(), cv.getAsString(COLUMN_COMMONS_LINK))
}
}
@Test
fun updateExistingLocationBookmark() {
whenever(client.delete(isA(), isNull(), isNull())).thenReturn(1)
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(1))
assertFalse(testObject.updateBookmarkLocation(examplePlaceBookmark))
verify(client).delete(eq(BookmarkLocationsContentProvider.uriForName(examplePlaceBookmark.name)), isNull(), isNull())
}
@Test
fun findExistingLocationBookmark() {
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(1))
assertTrue(testObject.findBookmarkLocation(examplePlaceBookmark))
}
@Test(expected = RuntimeException::class)
fun findLocationBookmarkTranslatesExceptions() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException(""))
testObject.findBookmarkLocation(examplePlaceBookmark)
}
@Test
fun findNotExistingLocationBookmarkReturnsNull_emptyCursor() {
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(0))
assertFalse(testObject.findBookmarkLocation(examplePlaceBookmark))
}
@Test
fun findNotExistingLocationBookmarkReturnsNull_nullCursor() {
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(null)
assertFalse(testObject.findBookmarkLocation(examplePlaceBookmark))
}
@Test
fun cursorsAreClosedAfterFindLocationBookmarkQuery() {
val mockCursor: Cursor = mock()
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(mockCursor)
whenever(mockCursor.moveToFirst()).thenReturn(false)
testObject.findBookmarkLocation(examplePlaceBookmark)
verify(mockCursor).close()
}
@Test
fun migrateTableVersionFrom_v1_to_v2() {
onUpdate(database, 1, 2)
// Table didnt exist before v5
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v2_to_v3() {
onUpdate(database, 2, 3)
// Table didnt exist before v5
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v3_to_v4() {
onUpdate(database, 3, 4)
// Table didnt exist before v5
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v4_to_v5() {
onUpdate(database, 4, 5)
// Table didnt change in version 5
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v5_to_v6() {
onUpdate(database, 5, 6)
// Table didnt change in version 6
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v6_to_v7() {
onUpdate(database, 6, 7)
// Table didnt change in version 7
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v7_to_v8() {
onUpdate(database, 7, 8)
verify(database).execSQL(CREATE_TABLE_STATEMENT)
}
private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply {
for (i in 0 until rowCount) {
addRow(listOf("placeName", "placeDescription","placeCategory", exampleLabel.text, exampleLabel.icon,
exampleUri, builder.build().wikipediaLink, builder.build().wikidataLink, builder.build().commonsLink,
exampleLocation.latitude, exampleLocation.longitude))
}
}
}

View file

@ -0,0 +1,217 @@
package fr.free.nrw.commons.bookmarks.pictures
import android.content.ContentProviderClient
import android.content.ContentValues
import android.database.Cursor
import android.database.MatrixCursor
import android.database.sqlite.SQLiteDatabase
import android.os.RemoteException
import com.nhaarman.mockito_kotlin.*
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.bookmarks.Bookmark
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.BASE_URI
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.*
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
@RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class)
class BookmarkPictureDaoTest {
private val columns = arrayOf(COLUMN_MEDIA_NAME, COLUMN_CREATOR)
private val client: ContentProviderClient = mock()
private val database: SQLiteDatabase = mock()
private val captor = argumentCaptor<ContentValues>()
private lateinit var testObject: BookmarkPicturesDao
private lateinit var exampleBookmark: Bookmark
@Before
fun setUp() {
exampleBookmark = Bookmark("mediaName", "creatorName")
testObject = BookmarkPicturesDao { client }
}
@Test
fun createTable() {
onCreate(database)
verify(database).execSQL(CREATE_TABLE_STATEMENT)
}
@Test
fun deleteTable() {
onDelete(database)
inOrder(database) {
verify(database).execSQL(DROP_TABLE_STATEMENT)
verify(database).execSQL(CREATE_TABLE_STATEMENT)
}
}
@Test
fun createFromCursor() {
createCursor(1).let { cursor ->
cursor.moveToFirst()
testObject.fromCursor(cursor).let {
assertEquals("mediaName", it.mediaName)
assertEquals("creatorName", it.mediaCreator)
}
}
}
@Test
fun getAllBookmarks() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(14))
var result = testObject.allBookmarks
assertEquals(14,(result.size))
}
@Test(expected = RuntimeException::class)
fun getAllBookmarksTranslatesExceptions() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException(""))
testObject.allBookmarks
}
@Test
fun getAllBookmarksReturnsEmptyList_emptyCursor() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(0))
assertTrue(testObject.allBookmarks.isEmpty())
}
@Test
fun getAllBookmarksReturnsEmptyList_nullCursor() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null)
assertTrue(testObject.allBookmarks.isEmpty())
}
@Test
fun cursorsAreClosedAfterGetAllBookmarksQuery() {
val mockCursor: Cursor = mock()
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor)
whenever(mockCursor.moveToFirst()).thenReturn(false)
testObject.allBookmarks
verify(mockCursor).close()
}
@Test
fun updateNewBookmark() {
whenever(client.insert(any(), any())).thenReturn(exampleBookmark.contentUri)
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(null)
assertTrue(testObject.updateBookmark(exampleBookmark))
verify(client).insert(eq(BASE_URI), captor.capture())
captor.firstValue.let { cv ->
assertEquals(2, cv.size())
assertEquals(exampleBookmark.mediaName, cv.getAsString(COLUMN_MEDIA_NAME))
assertEquals(exampleBookmark.mediaCreator, cv.getAsString(COLUMN_CREATOR))
}
}
@Test
fun updateExistingBookmark() {
whenever(client.delete(isA(), isNull(), isNull())).thenReturn(1)
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(1))
assertFalse(testObject.updateBookmark(exampleBookmark))
verify(client).delete(eq(exampleBookmark.contentUri), isNull(), isNull())
}
@Test
fun findExistingBookmark() {
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(1))
assertTrue(testObject.findBookmark(exampleBookmark))
}
@Test(expected = RuntimeException::class)
fun findBookmarkTranslatesExceptions() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException(""))
testObject.findBookmark(exampleBookmark)
}
@Test
fun findNotExistingBookmarkReturnsNull_emptyCursor() {
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(0))
assertFalse(testObject.findBookmark(exampleBookmark))
}
@Test
fun findNotExistingBookmarkReturnsNull_nullCursor() {
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(null)
assertFalse(testObject.findBookmark(exampleBookmark))
}
@Test
fun cursorsAreClosedAfterFindBookmarkQuery() {
val mockCursor: Cursor = mock()
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(mockCursor)
whenever(mockCursor.moveToFirst()).thenReturn(false)
testObject.findBookmark(exampleBookmark)
verify(mockCursor).close()
}
@Test
fun migrateTableVersionFrom_v1_to_v2() {
onUpdate(database, 1, 2)
// Table didnt exist before v5
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v2_to_v3() {
onUpdate(database, 2, 3)
// Table didnt exist before v5
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v3_to_v4() {
onUpdate(database, 3, 4)
// Table didnt exist before v5
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v4_to_v5() {
onUpdate(database, 4, 5)
// Table didnt change in version 5
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v5_to_v6() {
onUpdate(database, 5, 6)
// Table didnt change in version 6
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v6_to_v7() {
onUpdate(database, 6, 7)
// Table didnt change in version 7
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v7_to_v8() {
onUpdate(database, 7, 8)
verify(database).execSQL(CREATE_TABLE_STATEMENT)
}
private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply {
for (i in 0 until rowCount) {
addRow(listOf("mediaName", "creatorName"))
}
}
}

View file

@ -86,6 +86,20 @@ class CategoryDaoTest {
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v6_to_v7() {
onUpdate(database, 6, 7)
// Table didnt change in version 7
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v7_to_v8() {
onUpdate(database, 7, 8)
// Table didnt change in version 8
verifyZeroInteractions(database)
}
@Test
fun createFromCursor() {
createCursor(1).let { cursor ->

View file

@ -105,6 +105,20 @@ class ContributionDaoTest {
}
}
@Test
fun migrateTableVersionFrom_v6_to_v7() {
Table.onUpdate(database, 6, 7)
// Table didnt change in version 7
verifyZeroInteractions(database)
}
@Test
fun migrateTableVersionFrom_v7_to_v8() {
Table.onUpdate(database, 7, 8)
// Table didnt change in version 8
verifyZeroInteractions(database)
}
@Test
fun saveNewContribution_nonNullFields() {
whenever(client.insert(isA(), isA())).thenReturn(contentUri)