Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to mvvm Jovche #1

Open
wants to merge 85 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
1bea4c1
Introduce JUnit5
mitrejcevski Apr 8, 2022
4dacca4
Allow instantiations with invalid context (temporarly)
mitrejcevski Apr 8, 2022
f83e74b
Introduce a function to be leveraged as a seam
mitrejcevski Apr 8, 2022
0f1c12e
Introduce a seam
mitrejcevski Apr 8, 2022
5b53aa3
Leverage the seam to allow test-double substitution
mitrejcevski Apr 8, 2022
e3a5d45
Introduce test-double to substitute SharedPreferences
mitrejcevski Apr 8, 2022
c02c529
Instantiation test passing
mitrejcevski Apr 8, 2022
3754232
Rename the test to reflect the behavior it exercises
mitrejcevski Apr 8, 2022
88cddd1
Editing not working due to missing implementation in test-double
mitrejcevski Apr 8, 2022
83f5f73
Allow preferences editing
mitrejcevski Apr 8, 2022
5477591
Introduce proper preferences editor and storing mechanism in memory
mitrejcevski Apr 8, 2022
44a4e62
Cover the case for the B screen
mitrejcevski Apr 8, 2022
4615f33
Cover exlicit selection of C screen
mitrejcevski Apr 8, 2022
87eca11
Default viewing option
mitrejcevski Apr 8, 2022
8080da2
Cover ShowName viewing option
mitrejcevski Apr 8, 2022
f55593b
Cover Date viewing option
mitrejcevski Apr 8, 2022
8440e0e
Cover expicit selection of nothing view option
mitrejcevski Apr 8, 2022
79cdd1d
Default show valid dot
mitrejcevski Apr 8, 2022
ed1c26f
Cover setting show valid dot (functionallity covered)
mitrejcevski Apr 8, 2022
6aed8ba
Push the SharedPreferences property in the constructor
mitrejcevski Apr 8, 2022
b7cc37f
Make the property non-nullable
mitrejcevski Apr 8, 2022
3442e1f
Remove death code
mitrejcevski Apr 8, 2022
7d66e1d
Remove unused constructor property
mitrejcevski Apr 8, 2022
53457c3
Inline propery
mitrejcevski Apr 8, 2022
061d064
Use AppPreferences in the tests directly
mitrejcevski Apr 8, 2022
0ae30ee
Eliminate the seam
mitrejcevski Apr 8, 2022
1ec6dde
Clean-up
mitrejcevski Apr 8, 2022
dc17ff9
UI testing dependencies setup
mitrejcevski Apr 8, 2022
74d9cc4
Initial UI test setup
mitrejcevski Apr 8, 2022
6c773c3
Define sharedTest directory
mitrejcevski Apr 8, 2022
fec65ef
Move test-double inside sharedTest folder for reusability
mitrejcevski Apr 8, 2022
a25d300
Define custom test runner for UI tests
mitrejcevski Apr 8, 2022
e621fee
Leverage the shared test-double in the UI tests
mitrejcevski Apr 8, 2022
f8081db
Cover default values for SelectedScreen
mitrejcevski Apr 8, 2022
f1936de
Improve test name
mitrejcevski Apr 8, 2022
4be3ba6
Introduce screen robot to improve tests intention and clarity
mitrejcevski Apr 8, 2022
bc591ee
Improve test name
mitrejcevski Apr 8, 2022
8e7e41d
Cover default values for ViewingOption
mitrejcevski Apr 8, 2022
31a160d
Cover default value for valid dot option
mitrejcevski Apr 8, 2022
15f4697
Update test setup to enable more robust verification
mitrejcevski Apr 8, 2022
421c095
Cover select screen options interaction
mitrejcevski Apr 8, 2022
9a75efa
Cover select viewing options interactions
mitrejcevski Apr 8, 2022
025a76e
Cover show valid dot option interactions (functionallity covered)
mitrejcevski Apr 8, 2022
8a29b93
Delete default project-generated sample test files
mitrejcevski Apr 8, 2022
ac74444
Cover loading the selected screen state
mitrejcevski Apr 9, 2022
cc089f0
Cover various selected screen options selection
mitrejcevski Apr 9, 2022
ff27fde
Remove duplication
mitrejcevski Apr 9, 2022
717fe1c
Improve formatting
mitrejcevski Apr 9, 2022
d34fb3f
Cover storing selected start screen value
mitrejcevski Apr 9, 2022
bf51ec8
Cover loading the viewing option
mitrejcevski Apr 9, 2022
3146a16
Cover various viewing option selection
mitrejcevski Apr 9, 2022
b1ae00d
Improve formatting
mitrejcevski Apr 9, 2022
bd17da3
Cover storing selected viewing option value
mitrejcevski Apr 9, 2022
e84a0b6
Cover loading the valid dot option
mitrejcevski Apr 9, 2022
ee1f50f
Cover storing the selected viewing dot option value
mitrejcevski Apr 9, 2022
1399a5b
Cover storing valid dot option value to app preferences
mitrejcevski Apr 9, 2022
a88fbb8
Move classes out of the presenter file
mitrejcevski Apr 9, 2022
5e3a7ec
Remove internal selecScreenSubsject subscription
mitrejcevski Apr 9, 2022
d70e832
Remove internal viewingOptionSubject subscription
mitrejcevski Apr 9, 2022
eeb0823
Clean up empty disposable and the subjects
mitrejcevski Apr 9, 2022
0adeec2
Clean up uselsee subsjects from the AppPreferences
mitrejcevski Apr 9, 2022
a5cd690
Introduce LiveData for selected screen
mitrejcevski Apr 9, 2022
6d5560e
Introduce InstantTaskExecutionExtension (to be able to work with Live…
mitrejcevski Apr 9, 2022
3bd5b4f
Deliver selected screen value to the live data observers
mitrejcevski Apr 9, 2022
efa3e59
Set initial live data value from appPreferences
mitrejcevski Apr 9, 2022
bd7e7f2
Migrate screen selection options to live data
mitrejcevski Apr 9, 2022
3428f35
Remove screen selection flowable from presenter
mitrejcevski Apr 9, 2022
2ecd897
Introduce LiveData for viewing option
mitrejcevski Apr 9, 2022
573aa35
Deliver viewing option value to the live data observers
mitrejcevski Apr 9, 2022
681f446
Migrate viewing options to live data
mitrejcevski Apr 9, 2022
4f43ac8
Remove obsolete code
mitrejcevski Apr 9, 2022
d48e5c2
Remove ViewingOptionFlowable from presenter
mitrejcevski Apr 9, 2022
3981676
Remove RxJava completely
mitrejcevski Apr 9, 2022
b591423
Improve code organisation in MainActivity
mitrejcevski Apr 9, 2022
e814761
Rename presenter
mitrejcevski Apr 9, 2022
ef060fd
Migrate to proper ViewModel
mitrejcevski Apr 9, 2022
16c45cd
Introduce ScreenState (to be used to consolidate the properties)
mitrejcevski Apr 9, 2022
eb87b32
Deliver screen states to live data observers
mitrejcevski Apr 9, 2022
a0f85dc
Deliver ViewingOption with the ScreenState
mitrejcevski Apr 9, 2022
1b81917
Deliver validity dot option with screen state
mitrejcevski Apr 9, 2022
4a73845
Expose initial screen state loading
mitrejcevski Apr 9, 2022
1e4f53d
Migrate the activity to use the screen state live data
mitrejcevski Apr 9, 2022
cec646d
Remove unused code
mitrejcevski Apr 9, 2022
fa37fd3
Remove obsolete code
mitrejcevski Apr 9, 2022
93e265c
Migrate to ViewBinding
mitrejcevski Apr 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


plugins {
id 'kotlin-android'
id 'kotlin-android-extensions'
Expand All @@ -19,7 +17,7 @@ android {
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "software.ninetofive.assignment_tests.CustomTestRunner"
}

buildTypes {
Expand All @@ -28,34 +26,49 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

buildFeatures {
viewBinding true
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = '1.8'
}
}

ext.rxbindingVersion = '2.1.1'
sourceSets {
def sharedTestDirectory = "src/sharedTest/java"
test {
java.srcDir sharedTestDirectory
}
androidTest {
java.srcDir sharedTestDirectory
}
}
}

dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

// hilt
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"

implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "com.jakewharton.rxbinding2:rxbinding:$rxbindingVersion"
implementation "com.jakewharton.rxbinding2:rxbinding-support-v4:$rxbindingVersion"
implementation "com.jakewharton.rxbinding2:rxbinding-appcompat-v7:$rxbindingVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3"

androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"

testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.2"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.2"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package software.ninetofive.assignment_tests

import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication

class CustomTestRunner : AndroidJUnitRunner() {

override fun newApplication(
classLoader: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(classLoader, HiltTestApplication::class.java.name, context)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package software.ninetofive.assignment_tests.main

import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import software.ninetofive.assignment_tests.R
import software.ninetofive.assignment_tests.main.SelectedScreen.*
import software.ninetofive.assignment_tests.main.ViewingOption.*

fun launchMainScreen(
block: MainScreenRobot.() -> Unit
): MainScreenRobot {
ActivityScenario.launch(MainActivity::class.java)
return MainScreenRobot().apply(block)
}

class MainScreenRobot {

fun checkSelectedScreenHeaderShown() {
onView(withText(R.string.choose_start_screen_header)).check(matches(isDisplayed()))
}

fun tapOnScreenAOption() {
onView(withText(R.string.check_start_screen_a)).perform(click())
}

fun tapOnScreenBOption() {
onView(withText(R.string.check_start_screen_b)).perform(click())
}

fun tapOnScreenCOption() {
onView(withText(R.string.check_start_screen_c)).perform(click())
}

fun checkSelectedScreenOptionIs(selectedScreen: SelectedScreen) {
val screenASelected = if (selectedScreen == SCREEN_A) isChecked() else isNotChecked()
val screenBSelected = if (selectedScreen == SCREEN_B) isChecked() else isNotChecked()
val screenCSelected = if (selectedScreen == SCREEN_C) isChecked() else isNotChecked()

onView(withId(R.id.radio_screen_a)).check(matches(screenASelected))
onView(withId(R.id.radio_screen_b)).check(matches(screenBSelected))
onView(withId(R.id.radio_screen_c)).check(matches(screenCSelected))
}

fun checkViewingOptionsHeader() {
onView(withText(R.string.check_view_options_label)).check(matches(isDisplayed()))
}

fun tapOnShowNameOption() {
onView(withText(R.string.check_view_options_name)).perform(click())
}

fun tapOnDateOption() {
onView(withText(R.string.check_view_options_date)).perform(click())
}

fun tapOnNothingOption() {
onView(withText(R.string.check_view_options_nothing)).perform(click())
}

fun checkSelectedViewingOptionIs(viewingOption: ViewingOption) {
val showNameSelected = if (viewingOption == SHOW_NAME) isChecked() else isNotChecked()
val showDateSelected = if (viewingOption == DATE) isChecked() else isNotChecked()
val showNothingSelected = if (viewingOption == NOTHING) isChecked() else isNotChecked()

onView(withId(R.id.radio_show_name)).check(matches(showNameSelected))
onView(withId(R.id.radio_show_date)).check(matches(showDateSelected))
onView(withId(R.id.radio_show_nothing)).check(matches(showNothingSelected))
}

fun tapOnShowValidDot() {
onView(withText(R.string.show_valid_spot_settings_label)).perform(click())
}

fun checkShowValidDotOptionSelected(isSelected: Boolean) {
val selected = if (isSelected) isChecked() else isNotChecked()
onView(withId(R.id.valid_dot_switch)).check(matches(selected))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package software.ninetofive.assignment_tests.main

import androidx.test.ext.junit.runners.AndroidJUnit4
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import software.ninetofive.assignment_tests.utils.AppPreferences
import software.ninetofive.assignment_tests.utils.InMemorySharedPreferences

@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class MainScreenTest {

@get:Rule
val hiltRule = HiltAndroidRule(this)

@BindValue
val appPreferences = AppPreferences(InMemorySharedPreferences())

@Test
fun defaultSelectScreenOptionValue() {
launchMainScreen {
checkSelectedScreenHeaderShown()
//TODO Bug! Default preference (SCREEN_C) is recognised as B on screen
checkSelectedScreenOptionIs(SelectedScreen.SCREEN_B)
}
}

@Test
fun selectScreenOptionInteractions() {
appPreferences.setStartScreen(SelectedScreen.SCREEN_A)

launchMainScreen {
checkSelectedScreenHeaderShown()
checkSelectedScreenOptionIs(SelectedScreen.SCREEN_A)
tapOnScreenBOption()
checkSelectedScreenOptionIs(SelectedScreen.SCREEN_B)
tapOnScreenCOption()
checkSelectedScreenOptionIs(SelectedScreen.SCREEN_C)
tapOnScreenAOption()
checkSelectedScreenOptionIs(SelectedScreen.SCREEN_A)
}
}

@Test
fun defaultViewingOptionValue() {
launchMainScreen {
checkViewingOptionsHeader()
checkSelectedViewingOptionIs(ViewingOption.NOTHING)
}
}

@Test
fun selectViewingOptionInteractions() {
appPreferences.setViewingOption(ViewingOption.DATE)

launchMainScreen {
checkViewingOptionsHeader()
checkSelectedViewingOptionIs(ViewingOption.DATE)
tapOnNothingOption()
checkSelectedViewingOptionIs(ViewingOption.NOTHING)
tapOnShowNameOption()
checkSelectedViewingOptionIs(ViewingOption.SHOW_NAME)
tapOnDateOption()
checkSelectedViewingOptionIs(ViewingOption.DATE)
}
}

@Test
fun defaultShowValidDotValue() {
launchMainScreen {
checkShowValidDotOptionSelected(false)
}
}

@Test
fun showValidDotOptionInteractions() {
launchMainScreen {
checkShowValidDotOptionSelected(false)
tapOnShowValidDot()
checkShowValidDotOptionSelected(true)
tapOnShowValidDot()
checkShowValidDotOptionSelected(false)
}
}
}
Loading