Skip to content

Commit 0fe52d2

Browse files
authored
Merge pull request #150 from Abhiek187/feature/recent-recipes
Home Screen Accordions
2 parents ee695f2 + 0deaee5 commit 0fe52d2

File tree

29 files changed

+636
-220
lines changed

29 files changed

+636
-220
lines changed

EZRecipes/app/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ android {
7070
excludes += "/META-INF/{AL2.0,LGPL2.1}"
7171
}
7272
}
73+
sourceSets {
74+
// Adds exported schema location as test app assets
75+
getByName("androidTest").assets.srcDir("$projectDir/schemas")
76+
}
7377
}
7478

7579
dependencies {
@@ -131,6 +135,7 @@ dependencies {
131135
androidTestImplementation("androidx.test.espresso:espresso-intents:$espressoVersion")
132136
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
133137
androidTestImplementation("tools.fastlane:screengrab:2.1.1")
138+
androidTestImplementation("androidx.room:room-testing:$roomVersion")
134139

135140
debugImplementation("androidx.compose.ui:ui-tooling")
136141
debugImplementation("androidx.compose.ui:ui-test-manifest")
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"formatVersion": 1,
3+
"database": {
4+
"version": 2,
5+
"identityHash": "40054cff3f5e16de3818df0a80c8edb3",
6+
"entities": [
7+
{
8+
"tableName": "RecentRecipe",
9+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `recipe` TEXT NOT NULL, `isFavorite` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
10+
"fields": [
11+
{
12+
"fieldPath": "id",
13+
"columnName": "id",
14+
"affinity": "INTEGER",
15+
"notNull": true
16+
},
17+
{
18+
"fieldPath": "timestamp",
19+
"columnName": "timestamp",
20+
"affinity": "INTEGER",
21+
"notNull": true
22+
},
23+
{
24+
"fieldPath": "recipe",
25+
"columnName": "recipe",
26+
"affinity": "TEXT",
27+
"notNull": true
28+
},
29+
{
30+
"fieldPath": "isFavorite",
31+
"columnName": "isFavorite",
32+
"affinity": "INTEGER",
33+
"notNull": true,
34+
"defaultValue": "0"
35+
}
36+
],
37+
"primaryKey": {
38+
"autoGenerate": false,
39+
"columnNames": [
40+
"id"
41+
]
42+
},
43+
"indices": [],
44+
"foreignKeys": []
45+
}
46+
],
47+
"views": [],
48+
"setupQueries": [
49+
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
50+
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '40054cff3f5e16de3818df0a80c8edb3')"
51+
]
52+
}
53+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.abhiek.ezrecipes
2+
3+
import android.content.ContentValues
4+
import android.content.Context
5+
import android.database.sqlite.SQLiteDatabase
6+
import androidx.room.testing.MigrationTestHelper
7+
import androidx.sqlite.db.SupportSQLiteDatabase
8+
import androidx.test.core.app.ApplicationProvider
9+
import androidx.test.ext.junit.runners.AndroidJUnit4
10+
import androidx.test.platform.app.InstrumentationRegistry
11+
import com.abhiek.ezrecipes.data.models.Recipe
12+
import com.abhiek.ezrecipes.data.recipe.MockRecipeService
13+
import com.abhiek.ezrecipes.data.storage.AppDatabase
14+
import com.abhiek.ezrecipes.data.storage.Converters
15+
import com.abhiek.ezrecipes.utils.Constants
16+
import kotlinx.coroutines.test.runTest
17+
import org.junit.Assert.assertEquals
18+
import org.junit.Assert.assertFalse
19+
import org.junit.Rule
20+
import org.junit.Test
21+
import org.junit.runner.RunWith
22+
import java.io.IOException
23+
24+
@RunWith(AndroidJUnit4::class)
25+
internal class AppDatabaseMigrationTest {
26+
companion object {
27+
private const val TEST_DB = "migration-test"
28+
}
29+
30+
private val roomConstants = Constants.Room
31+
private val mockRecipes = MockRecipeService.recipes
32+
private val converters = Converters()
33+
private val context = ApplicationProvider.getApplicationContext<Context>()
34+
35+
@get:Rule
36+
val migrationTestHelper = MigrationTestHelper(
37+
InstrumentationRegistry.getInstrumentation(),
38+
AppDatabase::class.java
39+
)
40+
41+
private fun insertRecipe(recipe: Recipe, db: SupportSQLiteDatabase) {
42+
// Safer alternative to execSQL
43+
val values = ContentValues()
44+
values.put("id", recipe.id)
45+
values.put("timestamp", System.currentTimeMillis())
46+
values.put("recipe", converters.recipeToString(recipe))
47+
48+
db.insert(roomConstants.RECENT_RECIPE_TABLE, SQLiteDatabase.CONFLICT_REPLACE, values)
49+
}
50+
51+
@Test
52+
@Throws(IOException::class)
53+
fun migrateAll() = runTest {
54+
// Test each migration of the Room database to make sure recipe data is retained
55+
migrationTestHelper.createDatabase(TEST_DB, roomConstants.VERSION_INITIAL).apply {
56+
// DAO classes only work on the latest schema version
57+
for (recipe in mockRecipes) {
58+
insertRecipe(recipe, this)
59+
}
60+
61+
// Prepare for the next version.
62+
close()
63+
}
64+
65+
// Validate schema changes
66+
migrationTestHelper.runMigrationsAndValidate(
67+
TEST_DB,
68+
roomConstants.VERSION_IS_FAVORITE,
69+
true
70+
)
71+
72+
// Validate data was migrated properly
73+
val db = AppDatabase.getInstance(context, inMemory = true)
74+
// Clear any data stored from previous instrumented tests
75+
db.clearAllTables()
76+
// Close the database and release any stream resources when the test finishes
77+
migrationTestHelper.closeWhenFinished(db)
78+
79+
val recipes = db.recentRecipeDao().getAll()
80+
81+
for ((index, recipe) in recipes.withIndex()) {
82+
assertEquals(recipe.id, mockRecipes[index].id)
83+
assertFalse(recipe.isFavorite)
84+
}
85+
}
86+
}

EZRecipes/app/src/androidTest/java/com/abhiek/ezrecipes/EZRecipesInstrumentedTest.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,32 @@ internal class EZRecipesInstrumentedTest {
107107
screenshot("home-screen", shotNum)
108108
shotNum += 1
109109

110+
// The accordions should be present, but not display any recipes
111+
val favoriteAccordion = composeTestRule
112+
.onNodeWithText(activity.getString(R.string.profile_favorites))
113+
val recentAccordion = composeTestRule
114+
.onNodeWithText(activity.getString(R.string.profile_recently_viewed))
115+
val ratingAccordion = composeTestRule
116+
.onNodeWithText(activity.getString(R.string.profile_ratings))
117+
val signInMessage = composeTestRule
118+
.onNodeWithText(activity.getString(R.string.sign_in_for_recipes))
119+
composeTestRule
120+
.onAllNodesWithContentDescription(activity.getString(R.string.accordion_expand))
121+
.assertCountEquals(3)
122+
composeTestRule
123+
.onAllNodesWithContentDescription(activity.getString(R.string.accordion_collapse))
124+
.assertCountEquals(0)
125+
126+
favoriteAccordion.performClick()
127+
signInMessage.assertExists()
128+
favoriteAccordion.performClick()
129+
recentAccordion.performClick()
130+
signInMessage.assertDoesNotExist()
131+
recentAccordion.performClick()
132+
ratingAccordion.performClick()
133+
signInMessage.assertExists()
134+
ratingAccordion.performClick()
135+
110136
// After clicking the find recipe button, it should be disabled
111137
findRecipeButton.performClick()
112138
findRecipeButton.assertIsNotEnabled()

EZRecipes/app/src/androidTest/java/com/abhiek/ezrecipes/data/recipe/RecipeRepositoryTest.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,29 @@ internal class RecipeRepositoryTest {
122122
val newTimestamp = recentRecipeDao.getRecipeById(mockService.recipes[0].id)?.timestamp ?: -1
123123
assertTrue(newTimestamp > oldTimestamp)
124124
}
125+
126+
@Test
127+
fun toggleFavoriteRecentRecipe() = runTest {
128+
// Given a database with a favorite recipe
129+
val recipe = mockService.recipes[0]
130+
val recentRecipe = RecentRecipe(
131+
id = recipe.id,
132+
timestamp = System.currentTimeMillis(),
133+
recipe = recipe,
134+
isFavorite = true
135+
)
136+
recentRecipeDao.insert(recentRecipe)
137+
138+
// When the favorite attribute is toggled
139+
recipeRepository.toggleFavoriteRecentRecipe(recipe.id)
140+
141+
// Then the recipe's favorite status should be updated
142+
var newRecipe = recentRecipeDao.getRecipeById(recipe.id)
143+
assertNotNull(newRecipe)
144+
assertFalse(newRecipe!!.isFavorite)
145+
146+
recipeRepository.toggleFavoriteRecentRecipe(recipe.id)
147+
newRecipe = recentRecipeDao.getRecipeById(recipe.id)
148+
assertTrue(newRecipe!!.isFavorite)
149+
}
125150
}

EZRecipes/app/src/main/java/com/abhiek/ezrecipes/data/models/Cuisine.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ enum class Cuisine {
3737
CENTRAL_AMERICAN,
3838
BBQ,
3939
BARBECUE,
40+
SCANDINAVIAN,
4041
UNKNOWN;
4142

4243
override fun toString(): String {

EZRecipes/app/src/main/java/com/abhiek/ezrecipes/data/models/MealType.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ enum class MealType {
3131
COCKTAIL,
3232
MOCKTAIL,
3333
SEASONING,
34+
BATTER,
3435
UNKNOWN;
3536

3637
override fun toString(): String {
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.abhiek.ezrecipes.data.models
22

3+
import androidx.room.ColumnInfo
34
import androidx.room.Entity
45
import androidx.room.PrimaryKey
56
import com.abhiek.ezrecipes.utils.Constants
@@ -8,5 +9,6 @@ import com.abhiek.ezrecipes.utils.Constants
89
data class RecentRecipe(
910
@PrimaryKey val id: Int, // extract id from recipe to detect duplicates
1011
var timestamp: Long,
11-
var recipe: Recipe
12+
var recipe: Recipe,
13+
@ColumnInfo(defaultValue = "0") var isFavorite: Boolean = false // 0 = false, 1 = true
1214
)

EZRecipes/app/src/main/java/com/abhiek/ezrecipes/data/recipe/RecipeRepository.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,13 @@ class RecipeRepository(
119119
)
120120
recentRecipeDao.insert(newRecipe)
121121
}
122+
123+
suspend fun toggleFavoriteRecentRecipe(recipeId: Int) {
124+
if (recentRecipeDao == null) return
125+
// If the recipe doesn't exist, return early
126+
val existingRecipe = recentRecipeDao.getRecipeById(recipeId) ?: return
127+
128+
existingRecipe.isFavorite = !existingRecipe.isFavorite
129+
recentRecipeDao.insert(existingRecipe)
130+
}
122131
}

EZRecipes/app/src/main/java/com/abhiek/ezrecipes/data/storage/AppDatabase.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package com.abhiek.ezrecipes.data.storage
22

33
import android.content.Context
4-
import androidx.room.Database
5-
import androidx.room.Room
6-
import androidx.room.RoomDatabase
7-
import androidx.room.TypeConverters
4+
import androidx.room.*
85
import com.abhiek.ezrecipes.data.models.RecentRecipe
96
import com.abhiek.ezrecipes.utils.Constants
107

11-
@Database(entities = [RecentRecipe::class], version = Constants.Room.DATABASE_VERSION)
8+
@Database(
9+
entities = [RecentRecipe::class],
10+
version = Constants.Room.VERSION_IS_FAVORITE,
11+
autoMigrations = [
12+
// Add isFavorite column
13+
AutoMigration(
14+
from = Constants.Room.VERSION_INITIAL,
15+
to = Constants.Room.VERSION_IS_FAVORITE
16+
)
17+
]
18+
)
1219
@TypeConverters(Converters::class)
1320
abstract class AppDatabase: RoomDatabase() {
1421
abstract fun recentRecipeDao(): RecentRecipeDao

0 commit comments

Comments
 (0)