Skip to content

Commit

Permalink
[Release] Feature Instance - sub feature "Delete Interests" #33
Browse files Browse the repository at this point in the history
* [release note] - [feature-instance] --> sub feature complete "Delete Interests"
  • Loading branch information
syedahmedjamil committed Dec 30, 2023
2 parents f3985cb + 5e968cf commit 85f42e1
Show file tree
Hide file tree
Showing 29 changed files with 433 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Feature: Remove Interests
User can remove interests

Background:
Given I am on the "Instance" screen
And I add "reddit" as an interest
And I add "upwork" as an interest

Scenario: User removes interests from home screen
When I remove "reddit" as an interest
And I should not see "reddit" as an interest
And I should see "upwork" as an interest

12 changes: 6 additions & 6 deletions app/src/androidTest/assets/test.preferences_pb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

3
$f01c5292-b45b-4f4b-92d3-4c4ce0252138 * interest1
3
$159cf703-f220-4979-8590-b09288ea35a0 * interest2
3
$518926a5-0cef-4a1b-a6db-1f910a28e96d * interest3

interest1 * interest1

interest2 * interest2

interest3 * interest3
7 changes: 7 additions & 0 deletions app/src/androidTest/assets/test.preferences_with_uuid_pb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

3
$f01c5292-b45b-4f4b-92d3-4c4ce0252138 * interest1
3
$159cf703-f220-4979-8590-b09288ea35a0 * interest2
3
$518926a5-0cef-4a1b-a6db-1f910a28e96d * interest3
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.github.syedahmedjamil.pushernotif.usecases.AddInterestUseCase
import com.github.syedahmedjamil.pushernotif.usecases.GetInterestsUseCase
import com.github.syedahmedjamil.pushernotif.usecases.RemoveInterestUseCase
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
Expand All @@ -22,17 +23,25 @@ class AppContainerTest {

@Test
fun test_appContainer_has_a_addInterestUseCase_singleton() {
val addInterestUseCase: AddInterestUseCase = appContainer.addInterestUseCase
val addInterestUseCase2: AddInterestUseCase = appContainer.addInterestUseCase
assertNotNull(addInterestUseCase)
assertEquals(addInterestUseCase, addInterestUseCase2)
val useCase1: AddInterestUseCase = appContainer.addInterestUseCase
val useCase2: AddInterestUseCase = appContainer.addInterestUseCase
assertNotNull(useCase1)
assertEquals(useCase1, useCase2)
}

@Test
fun test_appContainer_has_a_getInterestsUseCase_singleton() {
val getInterestsUseCase: GetInterestsUseCase = appContainer.getInterestsUseCase
val getInterestsUseCase2: GetInterestsUseCase = appContainer.getInterestsUseCase
assertNotNull(getInterestsUseCase)
assertEquals(getInterestsUseCase, getInterestsUseCase2)
val useCase1: GetInterestsUseCase = appContainer.getInterestsUseCase
val useCase2: GetInterestsUseCase = appContainer.getInterestsUseCase
assertNotNull(useCase1)
assertEquals(useCase1, useCase2)
}

@Test
fun test_appContainer_has_a_removeInterestUseCase_singleton() {
val useCase1: RemoveInterestUseCase = appContainer.removeInterestUseCase
val useCase2: RemoveInterestUseCase = appContainer.removeInterestUseCase
assertNotNull(useCase1)
assertEquals(useCase1, useCase2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,61 @@ class InterestLocalDataSourceTest {
assertEquals(expected, actual)
}

@Test
fun should_remove_first_interest_when_interest_exists() = testScope.runTest {
// given
createTestDataStoreFile("test.preferences_pb")
val interest = "interest1"
val expected = listOf("interest2", "interest3")
// when
interestLocalDataSource.removeInterest(interest)
val actual = interestLocalDataSource.getInterests().first()
// then
assertEquals(expected, actual)
}

@Test
fun should_remove_middle_interest_when_interest_exists() = testScope.runTest {
// given
createTestDataStoreFile("test.preferences_pb")
val interest = "interest2"
val expected = listOf("interest1", "interest3")
// when
interestLocalDataSource.removeInterest(interest)
val actual = interestLocalDataSource.getInterests().first()
// then
assertEquals(expected, actual)
}

@Test
fun should_remove_last_interest_when_interest_exists() = testScope.runTest {
// given
createTestDataStoreFile("test.preferences_pb")
val interest = "interest3"
val expected = listOf("interest1", "interest2")
// when
interestLocalDataSource.removeInterest(interest)
val actual = interestLocalDataSource.getInterests().first()
// then
assertEquals(expected, actual)
}

@Test
fun should_remove_all_interest_when_interest_exists() = testScope.runTest {
// given
createTestDataStoreFile("test.preferences_pb")
val interest1 = "interest1"
val interest2 = "interest2"
val interest3 = "interest3"
val expected = listOf<String>()
// when
interestLocalDataSource.removeInterest(interest1)
interestLocalDataSource.removeInterest(interest2)
interestLocalDataSource.removeInterest(interest3)
val actual = interestLocalDataSource.getInterests().first()
// then
assertEquals(expected, actual)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import io.cucumber.java.Before
import io.cucumber.java.en.Given
import io.cucumber.java.en.Then
import io.cucumber.java.en.When
import io.cucumber.java.PendingException
import io.cucumber.java.en.And

class AddInterestsSteps {
class FeatureInstanceSteps {
private val dsl = CucumberDsl()

private lateinit var activityScenario: ActivityScenario<InstanceActivity>
Expand Down Expand Up @@ -47,4 +49,13 @@ class AddInterestsSteps {
dsl.instance.assertMessage(arg0)
}

@When("I remove {string} as an interest")
fun iRemoveAsAnInterest(arg0: String) {
dsl.instance.removeInterest(arg0)
}

@And("I should not see {string} as an interest")
fun iShouldNotSeeAsAnInterest(arg0: String) {
dsl.instance.assertInterestNotListed(arg0)
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
package com.github.syedahmedjamil.pushernotif.test.driver

import android.view.View
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ActivityScenario.ActivityAction
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers.withDecorView
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import com.github.syedahmedjamil.pushernotif.R
import com.github.syedahmedjamil.pushernotif.ui.instance.InstanceActivity
import io.cucumber.java.Before
import io.cucumber.java.BeforeAll
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.hasEntry
import org.hamcrest.Matchers.hasToString
import org.hamcrest.Matchers.hasValue
import org.hamcrest.Matchers.instanceOf
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.not
import org.junit.Rule
import org.hamcrest.Matchers.startsWith


class InstanceScreenDriver {
Expand All @@ -40,5 +39,16 @@ class InstanceScreenDriver {
onView(withId(com.google.android.material.R.id.snackbar_text))
.check(matches(withText(arg0)))
}

fun removeInterest(arg0: String) {
onData(allOf(`is`(instanceOf(String::class.java)), equalTo(arg0)))
.onChildView(withId(R.id.item_remove_icon))
.perform(click())
}

fun assertInterestNotListed(arg0: String) {
onView(withId(R.id.instance_interests_list_view))
.check(matches(not(hasDescendant(withText(arg0)))))
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@ class InstanceScreenDsl {
fun assertMessage(arg0: String) {
driver.assertMessage(arg0)
}

fun removeInterest(arg0: String) {
driver.removeInterest(arg0)
}

fun assertInterestNotListed(arg0: String) {
driver.assertInterestNotListed(arg0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.github.syedahmedjamil.pushernotif.data.InterestRepositoryImpl
import com.github.syedahmedjamil.pushernotif.framework.InterestLocalDataSource
import com.github.syedahmedjamil.pushernotif.usecases.AddInterestUseCaseImpl
import com.github.syedahmedjamil.pushernotif.usecases.GetInterestsUseCaseImpl
import com.github.syedahmedjamil.pushernotif.usecases.RemoveInterestUseCaseImpl
import kotlinx.coroutines.runBlocking

private const val INTEREST_DATASTORE_NAME = "interest"
Expand All @@ -26,6 +27,7 @@ class AppContainer(context: Context) {

val addInterestUseCase by lazy { AddInterestUseCaseImpl(interestRepository) }
val getInterestsUseCase by lazy { GetInterestsUseCaseImpl(interestRepository) }
val removeInterestUseCase by lazy { RemoveInterestUseCaseImpl(interestRepository) }

// For testing
@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class InterestLocalDataSource(private val dataStore: DataStore<Preferences>) : I

override suspend fun addInterest(interest: String) {
dataStore.edit { preferences ->
preferences[stringPreferencesKey(UUID.randomUUID().toString())] = interest
preferences[stringPreferencesKey(interest)] = interest
}
}

Expand All @@ -24,4 +24,10 @@ class InterestLocalDataSource(private val dataStore: DataStore<Preferences>) : I
return interests

}

override suspend fun removeInterest(interest: String) {
dataStore.edit {
it.remove(stringPreferencesKey(interest))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ class InstanceActivity : AppCompatActivity() {
this,
InstanceViewModel.InstanceViewModelFactory(
appContainer.addInterestUseCase,
appContainer.getInterestsUseCase
appContainer.getInterestsUseCase,
appContainer.removeInterestUseCase
)
)[InstanceViewModel::class.java]

//bindings
binding.viewmodel = viewModel
binding.adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1)
binding.adapter = InstanceInterestListAdapter(this, R.layout.interest_list_item, viewModel)

//observe
viewModel.errorMessage.observe(this) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.github.syedahmedjamil.pushernotif.ui.instance

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import com.github.syedahmedjamil.pushernotif.databinding.InterestListItemBinding


class InstanceInterestListAdapter(
context: Context,
resource: Int,
private val viewModel: InstanceViewModel
) :
ArrayAdapter<String>(context, resource) {

override fun getView(
position: Int,
convertView: View?,
parent: ViewGroup
): View {
val binding = convertView?.tag as? InterestListItemBinding ?:
InterestListItemBinding.inflate(LayoutInflater.from(context), parent, false)

val item = getItem(position)
binding.viewmodel = viewModel
binding.name = item

return binding.root
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import androidx.lifecycle.viewModelScope
import com.github.syedahmedjamil.pushernotif.core.Result
import com.github.syedahmedjamil.pushernotif.usecases.AddInterestUseCase
import com.github.syedahmedjamil.pushernotif.usecases.GetInterestsUseCase
import com.github.syedahmedjamil.pushernotif.usecases.RemoveInterestUseCase
import kotlinx.coroutines.launch

@Suppress("UNCHECKED_CAST")
class InstanceViewModel(
private val addInterestUseCase: AddInterestUseCase,
private val getInterestsUseCase: GetInterestsUseCase
private val getInterestsUseCase: GetInterestsUseCase,
private val removeInterestUseCase: RemoveInterestUseCase
) : ViewModel() {

private val _errorMessage = MutableLiveData<String>()
Expand All @@ -31,6 +33,15 @@ class InstanceViewModel(
}
}

fun removeInterest(interest: String) {
viewModelScope.launch {
val result = removeInterestUseCase(interest)
if (result is Result.Error) {
displayError(result.exception.message)
}
}
}

private fun displayError(message: String?) {
message?.let {
_errorMessage.value = it
Expand All @@ -39,10 +50,15 @@ class InstanceViewModel(

class InstanceViewModelFactory(
private val addInterestUseCase: AddInterestUseCase,
private val getInterestsUseCase: GetInterestsUseCase
private val getInterestsUseCase: GetInterestsUseCase,
private val removeInterestUseCase: RemoveInterestUseCase
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return InstanceViewModel(addInterestUseCase, getInterestsUseCase) as T
return InstanceViewModel(
addInterestUseCase,
getInterestsUseCase,
removeInterestUseCase
) as T
}
}
}
8 changes: 8 additions & 0 deletions app/src/main/res/drawable/ic_twotone_remove_circle_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<vector android:height="24dp" android:tint="#545454"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="0.3"
android:fillColor="@android:color/white"
android:pathData="M12,4c-4.41,0 -8,3.59 -8,8s3.59,8 8,8 8,-3.59 8,-8 -3.59,-8 -8,-8zM17,13L7,13v-2h10v2z" android:strokeAlpha="0.3"/>
<path android:fillColor="@android:color/white" android:pathData="M7,11h10v2L7,13zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>
Loading

0 comments on commit 85f42e1

Please sign in to comment.