diff --git a/app/src/androidTest/assets/features/add_interests.feature b/app/src/androidTest/assets/features/instance/add_interests.feature similarity index 100% rename from app/src/androidTest/assets/features/add_interests.feature rename to app/src/androidTest/assets/features/instance/add_interests.feature diff --git a/app/src/androidTest/assets/features/instance/remove_interests.feature b/app/src/androidTest/assets/features/instance/remove_interests.feature new file mode 100644 index 0000000..325b6d2 --- /dev/null +++ b/app/src/androidTest/assets/features/instance/remove_interests.feature @@ -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 + diff --git a/app/src/androidTest/assets/test.preferences_pb b/app/src/androidTest/assets/test.preferences_pb index e8f2c6f..3b28f41 100644 --- a/app/src/androidTest/assets/test.preferences_pb +++ b/app/src/androidTest/assets/test.preferences_pb @@ -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 \ No newline at end of file + + interest1 * interest1 + + interest2 * interest2 + + interest3 * interest3 \ No newline at end of file diff --git a/app/src/androidTest/assets/test.preferences_with_uuid_pb b/app/src/androidTest/assets/test.preferences_with_uuid_pb new file mode 100644 index 0000000..e8f2c6f --- /dev/null +++ b/app/src/androidTest/assets/test.preferences_with_uuid_pb @@ -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 \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/AppContainerTest.kt b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/AppContainerTest.kt index 09c4df3..e535087 100644 --- a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/AppContainerTest.kt +++ b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/AppContainerTest.kt @@ -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 @@ -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) } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/InterestLocalDataSourceTest.kt b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/InterestLocalDataSourceTest.kt index 7416843..f794769 100644 --- a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/InterestLocalDataSourceTest.kt +++ b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/InterestLocalDataSourceTest.kt @@ -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() + // when + interestLocalDataSource.removeInterest(interest1) + interestLocalDataSource.removeInterest(interest2) + interestLocalDataSource.removeInterest(interest3) + val actual = interestLocalDataSource.getInterests().first() + // then + assertEquals(expected, actual) + } + } \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/acceptance/AddInterestsSteps.kt b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/acceptance/FeatureInstanceSteps.kt similarity index 78% rename from app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/acceptance/AddInterestsSteps.kt rename to app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/acceptance/FeatureInstanceSteps.kt index 01512ba..0b8d9d9 100644 --- a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/acceptance/AddInterestsSteps.kt +++ b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/acceptance/FeatureInstanceSteps.kt @@ -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 @@ -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) + } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/driver/InstanceScreenDriver.kt b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/driver/InstanceScreenDriver.kt index 7e59dcd..f46d2fd 100644 --- a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/driver/InstanceScreenDriver.kt +++ b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/driver/InstanceScreenDriver.kt @@ -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 { @@ -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))))) + } } diff --git a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/dsl/InstanceScreenDsl.kt b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/dsl/InstanceScreenDsl.kt index 1ddc008..acf3f5a 100644 --- a/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/dsl/InstanceScreenDsl.kt +++ b/app/src/androidTest/java/com/github/syedahmedjamil/pushernotif/test/dsl/InstanceScreenDsl.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/syedahmedjamil/pushernotif/AppContainer.kt b/app/src/main/java/com/github/syedahmedjamil/pushernotif/AppContainer.kt index 356182b..15d342e 100644 --- a/app/src/main/java/com/github/syedahmedjamil/pushernotif/AppContainer.kt +++ b/app/src/main/java/com/github/syedahmedjamil/pushernotif/AppContainer.kt @@ -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" @@ -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 diff --git a/app/src/main/java/com/github/syedahmedjamil/pushernotif/framework/InterestLocalDataSource.kt b/app/src/main/java/com/github/syedahmedjamil/pushernotif/framework/InterestLocalDataSource.kt index 3e6677b..4282ac9 100644 --- a/app/src/main/java/com/github/syedahmedjamil/pushernotif/framework/InterestLocalDataSource.kt +++ b/app/src/main/java/com/github/syedahmedjamil/pushernotif/framework/InterestLocalDataSource.kt @@ -13,7 +13,7 @@ class InterestLocalDataSource(private val dataStore: DataStore) : I override suspend fun addInterest(interest: String) { dataStore.edit { preferences -> - preferences[stringPreferencesKey(UUID.randomUUID().toString())] = interest + preferences[stringPreferencesKey(interest)] = interest } } @@ -24,4 +24,10 @@ class InterestLocalDataSource(private val dataStore: DataStore) : I return interests } + + override suspend fun removeInterest(interest: String) { + dataStore.edit { + it.remove(stringPreferencesKey(interest)) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceActivity.kt b/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceActivity.kt index d0ad78e..3ea56dd 100644 --- a/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceActivity.kt +++ b/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceActivity.kt @@ -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(this, android.R.layout.simple_list_item_1) + binding.adapter = InstanceInterestListAdapter(this, R.layout.interest_list_item, viewModel) //observe viewModel.errorMessage.observe(this) { diff --git a/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceAdapter.kt b/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceAdapter.kt deleted file mode 100644 index be3fbca..0000000 --- a/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceAdapter.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.syedahmedjamil.pushernotif.ui.instance - -import com.github.syedahmedjamil.pushernotif.R -import android.content.Context -import android.widget.ArrayAdapter - - -// instance adapter with array adapter -//class InstanceAdapter(private val context: Context, private val interests: List) : -// ArrayAdapter(context, R.layout.instance_interests_list_view_item, interests) { -//} - diff --git a/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceInterestListAdapter.kt b/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceInterestListAdapter.kt new file mode 100644 index 0000000..91763b4 --- /dev/null +++ b/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceInterestListAdapter.kt @@ -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(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceViewModel.kt b/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceViewModel.kt index b7140b0..fcfa685 100644 --- a/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceViewModel.kt +++ b/app/src/main/java/com/github/syedahmedjamil/pushernotif/ui/instance/InstanceViewModel.kt @@ -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() @@ -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 @@ -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 create(modelClass: Class): T { - return InstanceViewModel(addInterestUseCase, getInterestsUseCase) as T + return InstanceViewModel( + addInterestUseCase, + getInterestsUseCase, + removeInterestUseCase + ) as T } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_twotone_remove_circle_24.xml b/app/src/main/res/drawable/ic_twotone_remove_circle_24.xml new file mode 100644 index 0000000..3bd8ca8 --- /dev/null +++ b/app/src/main/res/drawable/ic_twotone_remove_circle_24.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/layout/interest_list_item.xml b/app/src/main/res/layout/interest_list_item.xml new file mode 100644 index 0000000..92be8eb --- /dev/null +++ b/app/src/main/res/layout/interest_list_item.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/github/syedahmedjamil/pushernotif/InstanceViewModelTest.kt b/app/src/test/java/com/github/syedahmedjamil/pushernotif/InstanceViewModelTest.kt index 2df8de9..a02860e 100644 --- a/app/src/test/java/com/github/syedahmedjamil/pushernotif/InstanceViewModelTest.kt +++ b/app/src/test/java/com/github/syedahmedjamil/pushernotif/InstanceViewModelTest.kt @@ -4,7 +4,9 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.github.syedahmedjamil.pushernotif.core.Result import com.github.syedahmedjamil.pushernotif.shared_test.FakeAddInterestUseCase import com.github.syedahmedjamil.pushernotif.shared_test.FakeGetInterestsUseCase +import com.github.syedahmedjamil.pushernotif.shared_test.FakeRemoveInterestUseCase import com.github.syedahmedjamil.pushernotif.ui.instance.InstanceViewModel +import com.github.syedahmedjamil.pushernotif.usecases.RemoveInterestUseCase import com.github.syedahmedjamil.pushernotif.util.MainDispatcherRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle @@ -20,6 +22,7 @@ class InstanceViewModelTest { private lateinit var viewModel: InstanceViewModel private lateinit var addInterestUseCase: FakeAddInterestUseCase private lateinit var getInterestsUseCase: FakeGetInterestsUseCase + private lateinit var removeInterestUseCase: FakeRemoveInterestUseCase @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @@ -31,7 +34,9 @@ class InstanceViewModelTest { fun setUp() { addInterestUseCase = FakeAddInterestUseCase() getInterestsUseCase = FakeGetInterestsUseCase() - viewModel = InstanceViewModel(addInterestUseCase, getInterestsUseCase) + removeInterestUseCase = FakeRemoveInterestUseCase() + viewModel = + InstanceViewModel(addInterestUseCase, getInterestsUseCase, removeInterestUseCase) } @OptIn(ExperimentalCoroutinesApi::class) @@ -39,7 +44,7 @@ class InstanceViewModelTest { fun `should add interest`() = runTest { // given addInterestUseCase.isError = false - val interest = "interest1" + val interest = "interest" // when viewModel.addInterest(interest) advanceUntilIdle() @@ -83,5 +88,35 @@ class InstanceViewModelTest { assertEquals(expected, actual) } + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `should remove interest`() = runTest { + // given + removeInterestUseCase.isError = false + val interest = "interest" + // when + viewModel.removeInterest(interest) + advanceUntilIdle() + val result = removeInterestUseCase.result + // then + assertTrue(result is Result.Success) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `should display error when interest is not removed`() = runTest { + // given + removeInterestUseCase.isError = true + val interest = "interest" + val expected = "error" + // when + viewModel.removeInterest(interest) + advanceUntilIdle() + val result = removeInterestUseCase.result + val actual = viewModel.errorMessage.value + // then + assertTrue(result is Result.Error) + assertEquals(expected, actual) + } } \ No newline at end of file diff --git a/data/src/main/java/com/github/syedahmedjamil/pushernotif/data/InterestRepositoryImpl.kt b/data/src/main/java/com/github/syedahmedjamil/pushernotif/data/InterestRepositoryImpl.kt index 5c71550..e01019d 100644 --- a/data/src/main/java/com/github/syedahmedjamil/pushernotif/data/InterestRepositoryImpl.kt +++ b/data/src/main/java/com/github/syedahmedjamil/pushernotif/data/InterestRepositoryImpl.kt @@ -14,4 +14,8 @@ class InterestRepositoryImpl(private val interestLocalDataSource: InterestDataSo override fun getInterests(): Flow> { return interestLocalDataSource.getInterests() } + + override suspend fun removeInterest(interest: String) { + interestLocalDataSource.removeInterest(interest) + } } \ No newline at end of file diff --git a/data/src/test/kotlin/com/github/syedahmedjamil/pushernotif/data/InterestRepositoryTest.kt b/data/src/test/kotlin/com/github/syedahmedjamil/pushernotif/data/InterestRepositoryTest.kt index e0d4011..d6bd822 100644 --- a/data/src/test/kotlin/com/github/syedahmedjamil/pushernotif/data/InterestRepositoryTest.kt +++ b/data/src/test/kotlin/com/github/syedahmedjamil/pushernotif/data/InterestRepositoryTest.kt @@ -1,5 +1,6 @@ package com.github.syedahmedjamil.pushernotif.data +import com.github.syedahmedjamil.pushernotif.domain.InterestRepository import com.github.syedahmedjamil.pushernotif.shared_test.FakeInterestLocalDataSource import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest @@ -10,7 +11,7 @@ import org.junit.Test class InterestRepositoryTest { - private lateinit var interestRepository: InterestRepositoryImpl + private lateinit var interestRepository: InterestRepository private lateinit var fakeInterestLocalDataSource: FakeInterestLocalDataSource @Before @@ -24,7 +25,7 @@ class InterestRepositoryTest { @Test fun `should add interest`() = runTest { // given - val interest = "interest1" + val interest = "interest" // when interestRepository.addInterest(interest) // then @@ -40,4 +41,14 @@ class InterestRepositoryTest { // then assertEquals(expected, actual) } + + @Test + fun `should remove interest`() = runTest { + // given + val interest = "interest" + // when + interestRepository.removeInterest(interest) + // then + assertTrue(fakeInterestLocalDataSource.isRemoveInterestCalled) + } } \ No newline at end of file diff --git a/domain/src/main/java/com/github/syedahmedjamil/pushernotif/domain/InterestDataSource.kt b/domain/src/main/java/com/github/syedahmedjamil/pushernotif/domain/InterestDataSource.kt index d55555b..0d8958e 100644 --- a/domain/src/main/java/com/github/syedahmedjamil/pushernotif/domain/InterestDataSource.kt +++ b/domain/src/main/java/com/github/syedahmedjamil/pushernotif/domain/InterestDataSource.kt @@ -5,4 +5,5 @@ import kotlinx.coroutines.flow.Flow interface InterestDataSource { suspend fun addInterest(interest: String) fun getInterests(): Flow> + suspend fun removeInterest(interest: String) } \ No newline at end of file diff --git a/domain/src/main/java/com/github/syedahmedjamil/pushernotif/domain/InterestRepository.kt b/domain/src/main/java/com/github/syedahmedjamil/pushernotif/domain/InterestRepository.kt index 36b7a4a..9cab2bc 100644 --- a/domain/src/main/java/com/github/syedahmedjamil/pushernotif/domain/InterestRepository.kt +++ b/domain/src/main/java/com/github/syedahmedjamil/pushernotif/domain/InterestRepository.kt @@ -5,4 +5,5 @@ import kotlinx.coroutines.flow.Flow interface InterestRepository { suspend fun addInterest(interest: String) fun getInterests(): Flow> + suspend fun removeInterest(interest: String) } \ No newline at end of file diff --git a/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeInterestLocalDataSource.kt b/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeInterestLocalDataSource.kt index a0b0ccc..6a392d3 100644 --- a/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeInterestLocalDataSource.kt +++ b/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeInterestLocalDataSource.kt @@ -8,6 +8,7 @@ class FakeInterestLocalDataSource(private val interests: MutableList = m InterestDataSource { var isAddInterestCalled = false + var isRemoveInterestCalled = false override suspend fun addInterest(interest: String) { isAddInterestCalled = true @@ -16,4 +17,8 @@ class FakeInterestLocalDataSource(private val interests: MutableList = m override fun getInterests(): Flow> = flow { emit(interests) } + + override suspend fun removeInterest(interest: String) { + isRemoveInterestCalled = true + } } \ No newline at end of file diff --git a/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeInterestRepository.kt b/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeInterestRepository.kt index 25873ce..0a7b1e7 100644 --- a/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeInterestRepository.kt +++ b/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeInterestRepository.kt @@ -20,4 +20,10 @@ class FakeInterestRepository(val interests: MutableList = mutableListOf( emit(interests) } + override suspend fun removeInterest(interest: String) { + if (isSystemError) { + throw IOException() + } + } + } \ No newline at end of file diff --git a/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeRemoveInterestUseCase.kt b/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeRemoveInterestUseCase.kt new file mode 100644 index 0000000..eff42ce --- /dev/null +++ b/shared-test/src/main/java/com/github/syedahmedjamil/pushernotif/shared_test/FakeRemoveInterestUseCase.kt @@ -0,0 +1,19 @@ +package com.github.syedahmedjamil.pushernotif.shared_test + +import com.github.syedahmedjamil.pushernotif.core.Result +import com.github.syedahmedjamil.pushernotif.domain.InterestRepository +import com.github.syedahmedjamil.pushernotif.usecases.AddInterestUseCase +import com.github.syedahmedjamil.pushernotif.usecases.RemoveInterestUseCase +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class FakeRemoveInterestUseCase : RemoveInterestUseCase { + var isError = false + var result: Result = Result.Success(Unit) + override suspend operator fun invoke(interest: String): Result { + if (isError) + result = Result.Error(Exception("error")) + return result + } +} \ No newline at end of file diff --git a/usecases/src/main/java/com/github/syedahmedjamil/pushernotif/usecases/RemoveInterestUseCase.kt b/usecases/src/main/java/com/github/syedahmedjamil/pushernotif/usecases/RemoveInterestUseCase.kt new file mode 100644 index 0000000..9eaec80 --- /dev/null +++ b/usecases/src/main/java/com/github/syedahmedjamil/pushernotif/usecases/RemoveInterestUseCase.kt @@ -0,0 +1,7 @@ +package com.github.syedahmedjamil.pushernotif.usecases + +import com.github.syedahmedjamil.pushernotif.core.Result + +interface RemoveInterestUseCase { + suspend operator fun invoke(interest: String): Result +} \ No newline at end of file diff --git a/usecases/src/main/java/com/github/syedahmedjamil/pushernotif/usecases/RemoveInterestUseCaseImpl.kt b/usecases/src/main/java/com/github/syedahmedjamil/pushernotif/usecases/RemoveInterestUseCaseImpl.kt new file mode 100644 index 0000000..35cb890 --- /dev/null +++ b/usecases/src/main/java/com/github/syedahmedjamil/pushernotif/usecases/RemoveInterestUseCaseImpl.kt @@ -0,0 +1,32 @@ +package com.github.syedahmedjamil.pushernotif.usecases + +import com.github.syedahmedjamil.pushernotif.core.DuplicateInterestException +import com.github.syedahmedjamil.pushernotif.core.EmptyInterestException +import com.github.syedahmedjamil.pushernotif.core.NonBusinessException +import com.github.syedahmedjamil.pushernotif.core.Result +import com.github.syedahmedjamil.pushernotif.domain.InterestRepository +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext + + +class RemoveInterestUseCaseImpl( + private val repository: InterestRepository, + private val dispatcher: CoroutineDispatcher = Dispatchers.IO +) : RemoveInterestUseCase { + + private val nonBusinessErrorMessage = "NonBusinessError: error removing interest." + + override suspend operator fun invoke(interest: String): Result { + try { + withContext(dispatcher) { + repository.removeInterest(interest) + } + } catch (e: Exception) { + return Result.Error(NonBusinessException(nonBusinessErrorMessage)) + } + + return Result.Success(Unit) + } +} \ No newline at end of file diff --git a/usecases/src/test/kotlin/com/github/syedahmedjamil/pushernotif/usecases/AddInterestUseCaseTest.kt b/usecases/src/test/kotlin/com/github/syedahmedjamil/pushernotif/usecases/AddInterestUseCaseTest.kt index 8e40973..5040229 100644 --- a/usecases/src/test/kotlin/com/github/syedahmedjamil/pushernotif/usecases/AddInterestUseCaseTest.kt +++ b/usecases/src/test/kotlin/com/github/syedahmedjamil/pushernotif/usecases/AddInterestUseCaseTest.kt @@ -15,7 +15,7 @@ import org.junit.Before import org.junit.Test class AddInterestUseCaseTest { - private lateinit var useCase: AddInterestUseCaseImpl + private lateinit var useCase: AddInterestUseCase private lateinit var repository: FakeInterestRepository private val testDispatcher = StandardTestDispatcher() private val scope = TestScope(testDispatcher) diff --git a/usecases/src/test/kotlin/com/github/syedahmedjamil/pushernotif/usecases/RemoveInterestUseCaseTest.kt b/usecases/src/test/kotlin/com/github/syedahmedjamil/pushernotif/usecases/RemoveInterestUseCaseTest.kt new file mode 100644 index 0000000..be1518b --- /dev/null +++ b/usecases/src/test/kotlin/com/github/syedahmedjamil/pushernotif/usecases/RemoveInterestUseCaseTest.kt @@ -0,0 +1,52 @@ +package com.github.syedahmedjamil.pushernotif.usecases + +import com.github.syedahmedjamil.pushernotif.core.NonBusinessException +import com.github.syedahmedjamil.pushernotif.core.Result +import com.github.syedahmedjamil.pushernotif.shared_test.FakeInterestRepository +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class RemoveInterestUseCaseTest { + private lateinit var useCase: RemoveInterestUseCase + private lateinit var repository: FakeInterestRepository + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + + @Before + fun setUp() { + repository = FakeInterestRepository() + useCase = RemoveInterestUseCaseImpl(repository, testDispatcher) + } + + @Test + fun `should return success when interest is removed`() = scope.runTest { + // given + repository.isSystemError = false + val interest = "interest" + // when + val result = useCase(interest) + // then + assertTrue(result is Result.Success) + } + + @Test + fun `should return non-business error when interest is not removed`() = scope.runTest { + // given + repository.isSystemError = true + val interest = "interest" + val expected = "NonBusinessError: error removing interest." + // when + val result = useCase(interest) + // then + assertTrue(result is Result.Error) + result as Result.Error + assertTrue(result.exception is NonBusinessException) + val actual = result.exception.message + assertEquals(expected, actual) + } +} \ No newline at end of file