Add roboletric and improve testing
This commit is contained in:
parent
eb92414df1
commit
0de579010c
23 changed files with 470 additions and 84 deletions
|
@ -30,9 +30,25 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility 1.8
|
||||
sourceCompatibility 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
kapt {
|
||||
generateStubs true
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources true
|
||||
animationsDisabled true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -59,7 +75,20 @@ dependencies {
|
|||
kapt "com.google.dagger:dagger-android-processor:$versions.dagger"
|
||||
kapt "com.google.dagger:dagger-compiler:$versions.dagger"
|
||||
|
||||
testImplementation "com.google.dagger:dagger-android:$versions.dagger"
|
||||
testImplementation "com.google.dagger:dagger-android-support:$versions.dagger"
|
||||
|
||||
// Kotlin
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutines"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$versions.coroutines"
|
||||
|
||||
// Tests
|
||||
testImplementation "junit:junit:$versions.junit"
|
||||
testImplementation "androidx.test:core:$versions.testCore"
|
||||
testImplementation "androidx.test:core-ktx:$versions.testCore"
|
||||
testImplementation "androidx.test.espresso:espresso-core:$versions.espresso"
|
||||
testImplementation "androidx.test.espresso:espresso-contrib:$versions.espresso"
|
||||
testImplementation "androidx.fragment:fragment-testing:$versions.fragmentTest"
|
||||
testImplementation "org.robolectric:robolectric:$versions.robolectric"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package dev.lucasnlm.antimine
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.multidex.MultiDex
|
||||
import dagger.android.AndroidInjector
|
||||
|
@ -11,16 +12,20 @@ import dev.lucasnlm.antimine.di.AppModule
|
|||
import dev.lucasnlm.antimine.di.DaggerAppComponent
|
||||
import javax.inject.Inject
|
||||
|
||||
class MainApplication : DaggerApplication() {
|
||||
open class MainApplication : DaggerApplication() {
|
||||
|
||||
@Inject
|
||||
lateinit var analyticsManager: AnalyticsManager
|
||||
|
||||
protected open fun appModule(application: Application) = AppModule(application)
|
||||
|
||||
protected open fun levelModule(application: Application) = LevelModule(application)
|
||||
|
||||
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
|
||||
DaggerAppComponent.builder()
|
||||
.application(this)
|
||||
.appModule(AppModule(this))
|
||||
.levelModule(LevelModule(this))
|
||||
.appModule(appModule(this))
|
||||
.levelModule(levelModule(this))
|
||||
.build()
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
|
|
|
@ -47,7 +47,7 @@ class EndGameDialogFragment : DaggerAppCompatDialogFragment() {
|
|||
}
|
||||
|
||||
arguments?.run {
|
||||
isVictory = getBoolean(DIALOG_STATE) == true
|
||||
isVictory = getBoolean(DIALOG_IS_VICTORY) == true
|
||||
time = getLong(DIALOG_TIME)
|
||||
rightMines = getInt(DIALOG_RIGHT_MINES)
|
||||
totalMines = getInt(DIALOG_TOTAL_MINES)
|
||||
|
@ -134,14 +134,14 @@ class EndGameDialogFragment : DaggerAppCompatDialogFragment() {
|
|||
fun newInstance(victory: Boolean, rightMines: Int, totalMines: Int, time: Long): EndGameDialogFragment =
|
||||
EndGameDialogFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putBoolean(DIALOG_STATE, victory)
|
||||
putBoolean(DIALOG_IS_VICTORY, victory)
|
||||
putInt(DIALOG_RIGHT_MINES, rightMines)
|
||||
putInt(DIALOG_TOTAL_MINES, totalMines)
|
||||
putLong(DIALOG_TIME, time)
|
||||
}
|
||||
}
|
||||
|
||||
private const val DIALOG_STATE = "dialog_state"
|
||||
const val DIALOG_IS_VICTORY = "dialog_state"
|
||||
private const val DIALOG_TIME = "dialog_time"
|
||||
private const val DIALOG_RIGHT_MINES = "dialog_right_mines"
|
||||
private const val DIALOG_TOTAL_MINES = "dialog_total_mines"
|
||||
|
|
|
@ -17,18 +17,23 @@ class EngGameDialogViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun List<String>.safeRandomEmoji(except: String? = null, fallback: String = "\uD83D\uDCA3") =
|
||||
this.filter { it != except && checkGlyphAvailability(it) }
|
||||
.ifEmpty { listOf(fallback) }
|
||||
.random()
|
||||
|
||||
fun randomVictoryEmoji(except: String? = null) = listOf(
|
||||
"\uD83D\uDE00", "\uD83D\uDE0E", "\uD83D\uDE1D", "\uD83E\uDD73", "\uD83D\uDE06"
|
||||
).filter { it != except && checkGlyphAvailability(it) }.random()
|
||||
).safeRandomEmoji()
|
||||
|
||||
fun randomNeutralEmoji(except: String? = null) = listOf(
|
||||
"\uD83D\uDE01", "\uD83E\uDD14", "\uD83D\uDE42", "\uD83D\uDE09"
|
||||
).filter { it != except && checkGlyphAvailability(it) }.random()
|
||||
).safeRandomEmoji()
|
||||
|
||||
fun randomGameOverEmoji(except: String? = null) = listOf(
|
||||
"\uD83D\uDE10", "\uD83D\uDE44", "\uD83D\uDE25", "\uD83D\uDE13", "\uD83D\uDE31",
|
||||
"\uD83E\uDD2C", "\uD83E\uDD15", "\uD83D\uDE16", "\uD83D\uDCA3", "\uD83D\uDE05"
|
||||
).filter { it != except && checkGlyphAvailability(it) }.random()
|
||||
).safeRandomEmoji()
|
||||
|
||||
fun messageTo(context: Context, rightMines: Int, totalMines: Int, time: Long, isVictory: Boolean): String =
|
||||
if (totalMines != 0 && time != 0L) {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textAlignment="center"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title_emoji"
|
||||
|
@ -45,6 +46,7 @@
|
|||
android:textColor="@color/text_color"
|
||||
android:textSize="14sp"
|
||||
android:textAlignment="center"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||
|
|
12
app/src/test/java/dev/lucasnlm/antimine/TestApplication.kt
Normal file
12
app/src/test/java/dev/lucasnlm/antimine/TestApplication.kt
Normal file
|
@ -0,0 +1,12 @@
|
|||
package dev.lucasnlm.antimine
|
||||
|
||||
import android.app.Application
|
||||
import dev.lucasnlm.antimine.di.AppModule
|
||||
import dev.lucasnlm.antimine.di.TestLevelModule
|
||||
|
||||
class TestApplication : MainApplication() {
|
||||
|
||||
override fun appModule(application: Application) = AppModule(application)
|
||||
|
||||
override fun levelModule(application: Application) = TestLevelModule(application)
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package dev.lucasnlm.antimine.di
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dev.lucasnlm.antimine.common.level.di.LevelModule
|
||||
import dev.lucasnlm.antimine.common.level.models.Event
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModelFactory
|
||||
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import dev.lucasnlm.antimine.mocks.MockDimensionRepository
|
||||
import dev.lucasnlm.antimine.mocks.MockHapticFeedbackInteractor
|
||||
import dev.lucasnlm.antimine.mocks.MockMinefieldRepository
|
||||
import dev.lucasnlm.antimine.mocks.MockSavesRepository
|
||||
|
||||
@Module
|
||||
class TestLevelModule(
|
||||
application: Application
|
||||
) : LevelModule(application) {
|
||||
@Provides
|
||||
override fun provideGameEventObserver(): MutableLiveData<Event> = MutableLiveData()
|
||||
|
||||
@Provides
|
||||
override fun provideClock(): Clock = Clock()
|
||||
|
||||
@Provides
|
||||
override fun provideGameViewModelFactory(
|
||||
application: Application,
|
||||
eventObserver: MutableLiveData<Event>,
|
||||
savesRepository: ISavesRepository,
|
||||
dimensionRepository: IDimensionRepository,
|
||||
preferencesRepository: IPreferencesRepository,
|
||||
hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||
minefieldRepository: IMinefieldRepository,
|
||||
analyticsManager: AnalyticsManager,
|
||||
clock: Clock
|
||||
) = GameViewModelFactory(
|
||||
application,
|
||||
eventObserver,
|
||||
savesRepository,
|
||||
dimensionRepository,
|
||||
preferencesRepository,
|
||||
hapticFeedbackInteractor,
|
||||
minefieldRepository,
|
||||
analyticsManager,
|
||||
clock
|
||||
)
|
||||
|
||||
@Provides
|
||||
override fun provideDimensionRepository(
|
||||
context: Context,
|
||||
preferencesRepository: IPreferencesRepository
|
||||
): IDimensionRepository = MockDimensionRepository()
|
||||
|
||||
@Provides
|
||||
override fun provideSavesRepository(): ISavesRepository = MockSavesRepository()
|
||||
|
||||
@Provides
|
||||
override fun provideMinefieldRepository(): IMinefieldRepository = MockMinefieldRepository()
|
||||
|
||||
@Provides
|
||||
override fun provideHapticFeedbackInteractor(
|
||||
application: Application,
|
||||
preferencesRepository: IPreferencesRepository
|
||||
): IHapticFeedbackInteractor = MockHapticFeedbackInteractor()
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package dev.lucasnlm.antimine.level.view
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.core.app.launchActivity
|
||||
import dev.lucasnlm.antimine.GameActivity
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.TestApplication
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.LooperMode
|
||||
import org.robolectric.shadows.ShadowLooper
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(minSdk = 16, maxSdk = 27, application = TestApplication::class)
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
class LevelFragmentTest {
|
||||
@Test
|
||||
fun testShowGameOverWhenTapAMine() {
|
||||
launchActivity<GameActivity>().onActivity { activity ->
|
||||
ShadowLooper.runUiThreadTasks()
|
||||
|
||||
// First tap
|
||||
activity.findViewById<RecyclerView>(R.id.recyclerGrid)
|
||||
.findViewHolderForItemId(40).itemView.performClick()
|
||||
|
||||
ShadowLooper.runUiThreadTasks()
|
||||
|
||||
// Tap on a mine
|
||||
activity.findViewById<RecyclerView>(R.id.recyclerGrid)
|
||||
.findViewHolderForItemId(26).itemView.performClick()
|
||||
|
||||
ShadowLooper.idleMainLooper(2, TimeUnit.SECONDS)
|
||||
ShadowLooper.runUiThreadTasks()
|
||||
|
||||
val endGame = activity.supportFragmentManager.findFragmentByTag(EndGameDialogFragment.TAG)
|
||||
assertNotNull(endGame)
|
||||
assertEquals(endGame?.arguments?.get(EndGameDialogFragment.DIALOG_IS_VICTORY), false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowVictoryWhenTapAllSafeAreas() {
|
||||
val mines = sequenceOf(4, 9, 15, 26, 47, 53, 68, 71, 75)
|
||||
val safeAreas = (0 until 81).filterNot { mines.contains(it) }.map { it.toLong() }
|
||||
|
||||
launchActivity<GameActivity>().onActivity { activity ->
|
||||
ShadowLooper.runUiThreadTasks()
|
||||
|
||||
// First tap
|
||||
activity.findViewById<RecyclerView>(R.id.recyclerGrid)
|
||||
.findViewHolderForItemId(40).itemView.performClick()
|
||||
|
||||
ShadowLooper.runUiThreadTasks()
|
||||
|
||||
// Tap on safe places
|
||||
safeAreas.forEach { safeArea ->
|
||||
activity.findViewById<RecyclerView>(R.id.recyclerGrid)
|
||||
.findViewHolderForItemId(safeArea).itemView.performClick()
|
||||
ShadowLooper.runUiThreadTasks()
|
||||
}
|
||||
|
||||
ShadowLooper.idleMainLooper(2, TimeUnit.SECONDS)
|
||||
ShadowLooper.runUiThreadTasks()
|
||||
|
||||
val endGame = activity.supportFragmentManager.findFragmentByTag(EndGameDialogFragment.TAG)
|
||||
assertNotNull(endGame)
|
||||
assertEquals(endGame?.arguments?.get(EndGameDialogFragment.DIALOG_IS_VICTORY), true)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package dev.lucasnlm.antimine.mocks
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.Size
|
||||
|
||||
class MockDimensionRepository : IDimensionRepository {
|
||||
override fun areaSize(): Float = 50.0f
|
||||
|
||||
override fun displaySize(): Size = Size(50 * 20, 50 * 30)
|
||||
|
||||
override fun actionBarSize(): Int = 50
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package dev.lucasnlm.antimine.mocks
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||
|
||||
class MockHapticFeedbackInteractor : IHapticFeedbackInteractor {
|
||||
override fun toggleFlagFeedback() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
override fun explosionFeedback() {
|
||||
// Empty
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package dev.lucasnlm.antimine.mocks
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
|
||||
class MockMinefieldRepository : IMinefieldRepository {
|
||||
override fun fromDifficulty(
|
||||
difficulty: Difficulty,
|
||||
dimensionRepository: IDimensionRepository,
|
||||
preferencesRepository: IPreferencesRepository
|
||||
): Minefield = Minefield(9, 9, 9)
|
||||
|
||||
override fun randomSeed(): Long = 0L
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package dev.lucasnlm.antimine.mocks
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
|
||||
class MockPreferencesRepository : IPreferencesRepository {
|
||||
override fun customGameMode(): Minefield = Minefield(9, 9, 9)
|
||||
|
||||
override fun updateCustomGameMode(minefield: Minefield) { }
|
||||
|
||||
override fun getBoolean(key: String, defaultValue: Boolean): Boolean = false
|
||||
|
||||
override fun getInt(key: String, defaultValue: Int): Int = 0
|
||||
|
||||
override fun putBoolean(key: String, value: Boolean) { }
|
||||
|
||||
override fun putInt(key: String, value: Int) { }
|
||||
|
||||
override fun useFlagAssistant(): Boolean = true
|
||||
|
||||
override fun useHapticFeedback(): Boolean = false
|
||||
|
||||
override fun useLargeAreas(): Boolean = false
|
||||
|
||||
override fun useAnimations(): Boolean = false
|
||||
|
||||
override fun useDoubleClickToOpen(): Boolean = false
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package dev.lucasnlm.antimine.mocks
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
|
||||
class MockSavesRepository : ISavesRepository {
|
||||
override suspend fun fetchCurrentSave(): Save? {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun saveGame(save: Save): Long? {
|
||||
return 1
|
||||
}
|
||||
|
||||
override fun setLimit(maxSavesStorage: Int) {
|
||||
// Empty
|
||||
}
|
||||
}
|
|
@ -7,8 +7,8 @@ import dev.lucasnlm.antimine.common.level.models.Difficulty
|
|||
import dev.lucasnlm.antimine.common.level.models.Mark
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.common.level.models.Score
|
||||
import java.util.Random
|
||||
import kotlin.math.floor
|
||||
import kotlin.random.Random
|
||||
|
||||
class LevelFacade {
|
||||
private val minefield: Minefield
|
||||
|
@ -28,7 +28,7 @@ class LevelFacade {
|
|||
var mines: Sequence<Area> = sequenceOf()
|
||||
private set
|
||||
|
||||
constructor(minefield: Minefield, seed: Long = randomSeed()) {
|
||||
constructor(minefield: Minefield, seed: Long) {
|
||||
this.minefield = minefield
|
||||
this.randomGenerator = Random(seed)
|
||||
this.seed = seed
|
||||
|
@ -195,6 +195,7 @@ class LevelFacade {
|
|||
}
|
||||
|
||||
fun doubleClick(index: Int): Int = getArea(index).run {
|
||||
@Suppress("unused")
|
||||
return when {
|
||||
isCovered -> {
|
||||
openField(getArea(index))
|
||||
|
@ -212,6 +213,7 @@ class LevelFacade {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun longPressOpenArea(index: Int): Sequence<Area> {
|
||||
val neighbors = getArea(index).findNeighbors().filter {
|
||||
it.mark.isNone() && it.isCovered
|
||||
|
@ -356,8 +358,4 @@ class LevelFacade {
|
|||
fun setCurrentSaveId(id: Int) {
|
||||
this.saveId = id.coerceAtLeast(0)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun randomSeed(): Long = Random().nextLong()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import dagger.Module
|
|||
import dagger.Provides
|
||||
import dev.lucasnlm.antimine.common.level.models.Event
|
||||
import dev.lucasnlm.antimine.common.level.database.AppDataBase
|
||||
import dev.lucasnlm.antimine.common.level.database.dao.SaveDao
|
||||
import dev.lucasnlm.antimine.common.level.repository.DimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.SavesRepository
|
||||
|
@ -22,7 +22,7 @@ import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
|
|||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
|
||||
@Module
|
||||
class LevelModule(
|
||||
open class LevelModule(
|
||||
private val application: Application
|
||||
) {
|
||||
private val appDataBase by lazy {
|
||||
|
@ -40,20 +40,20 @@ class LevelModule(
|
|||
}
|
||||
|
||||
@Provides
|
||||
fun provideGameEventObserver(): MutableLiveData<Event> = MutableLiveData()
|
||||
open fun provideGameEventObserver(): MutableLiveData<Event> = MutableLiveData()
|
||||
|
||||
@Provides
|
||||
fun provideClock(): Clock = Clock()
|
||||
open fun provideClock(): Clock = Clock()
|
||||
|
||||
@Provides
|
||||
fun provideGameViewModelFactory(
|
||||
open fun provideGameViewModelFactory(
|
||||
application: Application,
|
||||
eventObserver: MutableLiveData<Event>,
|
||||
savesRepository: ISavesRepository,
|
||||
dimensionRepository: IDimensionRepository,
|
||||
preferencesRepository: IPreferencesRepository,
|
||||
hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||
minefieldRepository: MinefieldRepository,
|
||||
minefieldRepository: IMinefieldRepository,
|
||||
analyticsManager: AnalyticsManager,
|
||||
clock: Clock
|
||||
) = GameViewModelFactory(
|
||||
|
@ -69,26 +69,20 @@ class LevelModule(
|
|||
)
|
||||
|
||||
@Provides
|
||||
fun provideDimensionRepository(
|
||||
open fun provideDimensionRepository(
|
||||
context: Context,
|
||||
preferencesRepository: IPreferencesRepository
|
||||
): IDimensionRepository =
|
||||
DimensionRepository(context, preferencesRepository)
|
||||
|
||||
@Provides
|
||||
fun provideDataBase(): AppDataBase = appDataBase
|
||||
open fun provideSavesRepository(): ISavesRepository = savesRepository
|
||||
|
||||
@Provides
|
||||
fun provideSaveDao(): SaveDao = savesDao
|
||||
open fun provideMinefieldRepository(): IMinefieldRepository = MinefieldRepository()
|
||||
|
||||
@Provides
|
||||
fun provideSavesRepository(): ISavesRepository = savesRepository
|
||||
|
||||
@Provides
|
||||
fun provideMinefieldRepository(): MinefieldRepository = MinefieldRepository()
|
||||
|
||||
@Provides
|
||||
fun provideHapticFeedbackInteractor(
|
||||
open fun provideHapticFeedbackInteractor(
|
||||
application: Application,
|
||||
preferencesRepository: IPreferencesRepository
|
||||
): IHapticFeedbackInteractor =
|
||||
|
|
|
@ -3,16 +3,20 @@ package dev.lucasnlm.antimine.common.level.repository
|
|||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.content.res.TypedArray
|
||||
import android.util.DisplayMetrics
|
||||
import dev.lucasnlm.antimine.common.R
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
|
||||
interface IDimensionRepository {
|
||||
fun areaSize(): Float
|
||||
fun displaySize(): DisplayMetrics
|
||||
fun displaySize(): Size
|
||||
fun actionBarSize(): Int
|
||||
}
|
||||
|
||||
data class Size(
|
||||
val width: Int,
|
||||
val height: Int
|
||||
)
|
||||
|
||||
class DimensionRepository(
|
||||
private val context: Context,
|
||||
private val preferencesRepository: IPreferencesRepository
|
||||
|
@ -24,7 +28,9 @@ class DimensionRepository(
|
|||
context.resources.getDimension(R.dimen.field_size)
|
||||
}
|
||||
|
||||
override fun displaySize(): DisplayMetrics = Resources.getSystem().displayMetrics
|
||||
override fun displaySize(): Size = with(Resources.getSystem().displayMetrics) {
|
||||
return Size(this.widthPixels, this.heightPixels)
|
||||
}
|
||||
|
||||
override fun actionBarSize(): Int {
|
||||
val styledAttributes: TypedArray =
|
||||
|
|
|
@ -3,6 +3,7 @@ package dev.lucasnlm.antimine.common.level.repository
|
|||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import kotlin.random.Random
|
||||
|
||||
interface IMinefieldRepository {
|
||||
fun fromDifficulty(
|
||||
|
@ -10,6 +11,8 @@ interface IMinefieldRepository {
|
|||
dimensionRepository: IDimensionRepository,
|
||||
preferencesRepository: IPreferencesRepository
|
||||
): Minefield
|
||||
|
||||
fun randomSeed(): Long
|
||||
}
|
||||
|
||||
class MinefieldRepository : IMinefieldRepository {
|
||||
|
@ -46,11 +49,9 @@ class MinefieldRepository : IMinefieldRepository {
|
|||
val fieldSize = dimensionRepository.areaSize()
|
||||
|
||||
val display = dimensionRepository.displaySize()
|
||||
val width = display.widthPixels
|
||||
val height = display.heightPixels
|
||||
|
||||
val finalWidth = ((width / fieldSize).toInt() - 1).coerceAtLeast(6)
|
||||
val finalHeight = ((height / fieldSize).toInt() - 3).coerceAtLeast(9)
|
||||
val finalWidth = ((display.width / fieldSize).toInt() - 1).coerceAtLeast(6)
|
||||
val finalHeight = ((display.height / fieldSize).toInt() - 3).coerceAtLeast(9)
|
||||
|
||||
return Minefield(
|
||||
finalWidth,
|
||||
|
@ -58,4 +59,6 @@ class MinefieldRepository : IMinefieldRepository {
|
|||
(finalWidth * finalHeight * 0.2).toInt()
|
||||
)
|
||||
}
|
||||
|
||||
override fun randomSeed(): Long = Random.nextLong()
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.app.Application
|
|||
import android.os.Handler
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.LevelFacade
|
||||
import dev.lucasnlm.antimine.common.level.models.Area
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
|
@ -12,6 +11,7 @@ import dev.lucasnlm.antimine.common.level.models.Event
|
|||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||
|
@ -31,7 +31,7 @@ class GameViewModel(
|
|||
private val dimensionRepository: IDimensionRepository,
|
||||
private val preferencesRepository: IPreferencesRepository,
|
||||
private val hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||
private val minefieldRepository: MinefieldRepository,
|
||||
private val minefieldRepository: IMinefieldRepository,
|
||||
private val analyticsManager: AnalyticsManager,
|
||||
private val clock: Clock
|
||||
) : ViewModel() {
|
||||
|
@ -55,7 +55,7 @@ class GameViewModel(
|
|||
newDifficulty, dimensionRepository, preferencesRepository
|
||||
)
|
||||
|
||||
levelFacade = LevelFacade(minefield)
|
||||
levelFacade = LevelFacade(minefield, minefieldRepository.randomSeed())
|
||||
|
||||
mineCount.postValue(minefield.mines)
|
||||
difficulty.postValue(newDifficulty)
|
||||
|
|
|
@ -6,8 +6,8 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import dev.lucasnlm.antimine.common.level.models.Event
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
|
||||
|
@ -21,14 +21,14 @@ class GameViewModelFactory @Inject constructor(
|
|||
private val dimensionRepository: IDimensionRepository,
|
||||
private val preferencesRepository: IPreferencesRepository,
|
||||
private val hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||
private val minefieldRepository: MinefieldRepository,
|
||||
private val minefieldRepository: IMinefieldRepository,
|
||||
private val analyticsManager: AnalyticsManager,
|
||||
private val clock: Clock
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||
if (modelClass.isAssignableFrom(GameViewModel::class.java)) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
GameViewModel(
|
||||
application,
|
||||
eventObserver,
|
||||
|
|
|
@ -58,7 +58,7 @@ class LevelFacadeTest {
|
|||
plantMinesExcept(3)
|
||||
assertNotEquals(field.filter { it.hasMine }.map { it.id }.first(), 3)
|
||||
field.forEach {
|
||||
if (it.id == 7) {
|
||||
if (it.id == 6) {
|
||||
assertTrue(it.hasMine)
|
||||
} else {
|
||||
assertFalse(it.hasMine)
|
||||
|
@ -84,35 +84,34 @@ class LevelFacadeTest {
|
|||
assertTrue(
|
||||
levelFacadeOf(3, 3, 1, 200L).apply {
|
||||
plantMinesExcept(3)
|
||||
}.at(7).hasMine
|
||||
}.at(6).hasMine
|
||||
)
|
||||
assertTrue(
|
||||
levelFacadeOf(3, 3, 1, 250L).apply {
|
||||
plantMinesExcept(3)
|
||||
}.at(0).hasMine
|
||||
}.at(1).hasMine
|
||||
)
|
||||
assertTrue(
|
||||
levelFacadeOf(3, 3, 1, 100L).apply {
|
||||
plantMinesExcept(3)
|
||||
}.at(8).hasMine
|
||||
}.at(4).hasMine
|
||||
)
|
||||
assertTrue(
|
||||
levelFacadeOf(3, 3, 1, 170L).apply {
|
||||
plantMinesExcept(3)
|
||||
println(field)
|
||||
}.at(2).hasMine
|
||||
}.at(6).hasMine
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMineTips() {
|
||||
levelFacadeOf(3, 3, 1, 200L).run {
|
||||
levelFacadeOf(3, 3, 1, 150L).run {
|
||||
plantMinesExcept(3)
|
||||
assertEquals(
|
||||
listOf(
|
||||
0, 0, 0,
|
||||
1, 0, 1,
|
||||
1, 1, 1,
|
||||
1, 0, 1
|
||||
0, 0, 0
|
||||
),
|
||||
field.map { it.minesAround }.toList()
|
||||
)
|
||||
|
@ -122,9 +121,9 @@ class LevelFacadeTest {
|
|||
plantMinesExcept(3)
|
||||
assertEquals(
|
||||
listOf(
|
||||
1, 0, 1,
|
||||
2, 2, 2,
|
||||
1, 0, 1
|
||||
0, 1, 1,
|
||||
1, 2, 0,
|
||||
0, 2, 1
|
||||
),
|
||||
field.map { it.minesAround }.toList()
|
||||
)
|
||||
|
@ -134,9 +133,9 @@ class LevelFacadeTest {
|
|||
plantMinesExcept(3)
|
||||
assertEquals(
|
||||
listOf(
|
||||
0, 0, 1,
|
||||
3, 3, 2,
|
||||
1, 0, 1
|
||||
0, 2, 0,
|
||||
1, 3, 0,
|
||||
0, 2, 1
|
||||
),
|
||||
field.map { it.minesAround }.toList()
|
||||
)
|
||||
|
@ -146,10 +145,10 @@ class LevelFacadeTest {
|
|||
plantMinesExcept(3)
|
||||
assertEquals(
|
||||
listOf(
|
||||
0, 0, 0, 1,
|
||||
3, 0, 3, 1,
|
||||
2, 3, 2, 1,
|
||||
0, 2, 0, 1
|
||||
0, 0, 2, 1,
|
||||
0, 5, 0, 2,
|
||||
2, 4, 0, 2,
|
||||
0, 2, 1, 1
|
||||
),
|
||||
field.map { it.minesAround }.toList()
|
||||
)
|
||||
|
@ -159,10 +158,10 @@ class LevelFacadeTest {
|
|||
plantMinesExcept(3)
|
||||
assertEquals(
|
||||
listOf(
|
||||
0, 0, 1, 0,
|
||||
2, 2, 1, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0
|
||||
0, 1, 1, 1,
|
||||
0, 2, 0, 2,
|
||||
0, 2, 0, 2,
|
||||
0, 1, 1, 1
|
||||
),
|
||||
field.map { it.minesAround }.toList()
|
||||
)
|
||||
|
@ -172,10 +171,10 @@ class LevelFacadeTest {
|
|||
plantMinesExcept(3)
|
||||
assertEquals(
|
||||
listOf(
|
||||
1, 0, 1, 0,
|
||||
1, 1, 1, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0
|
||||
0, 1, 1, 1,
|
||||
0, 1, 0, 1,
|
||||
0, 1, 1, 1
|
||||
),
|
||||
field.map { it.minesAround }.toList()
|
||||
)
|
||||
|
@ -324,11 +323,11 @@ class LevelFacadeTest {
|
|||
|
||||
@Test
|
||||
fun testOpenSafeZone() {
|
||||
levelFacadeOf(3, 3, 1, 200L).run {
|
||||
levelFacadeOf(3, 3, 1, 0).run {
|
||||
plantMinesExcept(3)
|
||||
assertEquals(field.filter { it.isCovered }.count(), field.count())
|
||||
assertEquals(field.filterNot { it.isCovered }.count(), 0)
|
||||
singleClick(1)
|
||||
assertEquals(field.filter { it.isCovered }.count(), field.count() - 6)
|
||||
assertEquals(field.filterNot { it.isCovered }.count(), 6)
|
||||
assertEquals(
|
||||
field.filterNot { it.isCovered }.map { it.id }.toList(),
|
||||
listOf(0, 1, 2, 3, 4, 5)
|
||||
|
@ -381,7 +380,7 @@ class LevelFacadeTest {
|
|||
plantMinesExcept(3)
|
||||
val mine = field.last { it.hasMine }
|
||||
assertEquals(
|
||||
listOf(35, 33, 22, 27, 17, 32, 25, 8, 7, 2),
|
||||
listOf(33, 26, 28, 31, 19, 14, 18, 7, 6, 4),
|
||||
takeExplosionRadius(mine).map { it.id }.toList()
|
||||
)
|
||||
}
|
||||
|
@ -390,7 +389,7 @@ class LevelFacadeTest {
|
|||
plantMinesExcept(3)
|
||||
val mine = field.first { it.hasMine }
|
||||
assertEquals(
|
||||
listOf(2, 8, 7, 17, 22, 25, 27, 32, 33, 35),
|
||||
listOf(4, 14, 7, 28, 6, 19, 26, 18, 33, 31),
|
||||
takeExplosionRadius(mine).map { it.id }.toList()
|
||||
)
|
||||
}
|
||||
|
@ -399,7 +398,7 @@ class LevelFacadeTest {
|
|||
plantMinesExcept(3)
|
||||
val mine = field.filter { it.hasMine }.elementAt(4)
|
||||
assertEquals(
|
||||
listOf(22, 17, 27, 33, 35, 8, 32, 25, 2, 7),
|
||||
listOf(18, 19, 6, 7, 14, 26, 31, 33, 28, 4),
|
||||
takeExplosionRadius(mine).map { it.id }.toList()
|
||||
)
|
||||
}
|
||||
|
@ -485,7 +484,7 @@ class LevelFacadeTest {
|
|||
val width = 12
|
||||
val height = 12
|
||||
val mines = 9
|
||||
levelFacadeOf(width, height, mines, 200L).run {
|
||||
levelFacadeOf(width, height, mines, 150L).run {
|
||||
plantMinesExcept(3)
|
||||
field.filterNot { it.hasMine }.take(width * height - 1 - mines).forEach {
|
||||
singleClick(it.id)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package dev.lucasnlm.antimine.common.level
|
||||
|
||||
import android.util.DisplayMetrics
|
||||
import com.nhaarman.mockitokotlin2.doReturn
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.Size
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
@ -74,10 +74,7 @@ class MinefieldFactoryTest {
|
|||
val dimensionRepository: IDimensionRepository = mock {
|
||||
on { areaSize() } doReturn 10.0f
|
||||
on { actionBarSize() } doReturn 10
|
||||
on { displaySize() } doReturn DisplayMetrics().apply {
|
||||
widthPixels = 500
|
||||
heightPixels = 1000
|
||||
}
|
||||
on { displaySize() } doReturn Size(500, 1000)
|
||||
}
|
||||
|
||||
MinefieldRepository().fromDifficulty(
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package dev.lucasnlm.antimine.common.level.di
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Event
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.Size
|
||||
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModelFactory
|
||||
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
|
||||
@Module
|
||||
class TestLevelModule(
|
||||
private val application: Application
|
||||
) {
|
||||
@Provides
|
||||
fun provideGameEventObserver(): MutableLiveData<Event> = MutableLiveData()
|
||||
|
||||
@Provides
|
||||
fun provideClock(): Clock = Clock()
|
||||
|
||||
@Provides
|
||||
fun provideGameViewModelFactory(
|
||||
application: Application,
|
||||
eventObserver: MutableLiveData<Event>,
|
||||
savesRepository: ISavesRepository,
|
||||
dimensionRepository: IDimensionRepository,
|
||||
preferencesRepository: IPreferencesRepository,
|
||||
hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||
minefieldRepository: MinefieldRepository,
|
||||
analyticsManager: AnalyticsManager,
|
||||
clock: Clock
|
||||
) = GameViewModelFactory(
|
||||
application,
|
||||
eventObserver,
|
||||
savesRepository,
|
||||
dimensionRepository,
|
||||
preferencesRepository,
|
||||
hapticFeedbackInteractor,
|
||||
minefieldRepository,
|
||||
analyticsManager,
|
||||
clock
|
||||
)
|
||||
|
||||
@Provides
|
||||
fun provideDimensionRepository(
|
||||
context: Context,
|
||||
preferencesRepository: IPreferencesRepository
|
||||
): IDimensionRepository = object : IDimensionRepository {
|
||||
override fun areaSize(): Float = 50.0f
|
||||
|
||||
override fun displaySize(): Size = Size(50 * 15, 50 * 30)
|
||||
|
||||
override fun actionBarSize(): Int = 50
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideSavesRepository(): ISavesRepository = object : ISavesRepository {
|
||||
override suspend fun fetchCurrentSave(): Save? = null
|
||||
|
||||
override suspend fun saveGame(save: Save): Long? = null
|
||||
|
||||
override fun setLimit(maxSavesStorage: Int) { }
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideMinefieldRepository(): IMinefieldRepository = object : IMinefieldRepository {
|
||||
override fun fromDifficulty(
|
||||
difficulty: Difficulty,
|
||||
dimensionRepository: IDimensionRepository,
|
||||
preferencesRepository: IPreferencesRepository
|
||||
) = Minefield(9, 9, 9)
|
||||
|
||||
override fun randomSeed(): Long = 200
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideHapticFeedbackInteractor(
|
||||
application: Application,
|
||||
preferencesRepository: IPreferencesRepository
|
||||
): IHapticFeedbackInteractor = object : IHapticFeedbackInteractor {
|
||||
override fun toggleFlagFeedback() { }
|
||||
|
||||
override fun explosionFeedback() { }
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ ext.versions = [
|
|||
|
||||
// Kotlin
|
||||
kotlin : '1.3.70',
|
||||
coroutines : '1.3.4',
|
||||
coroutines : '1.3.5',
|
||||
|
||||
// Jetpack
|
||||
lifecycle : '1.1.1',
|
||||
|
@ -47,5 +47,8 @@ ext.versions = [
|
|||
junit : '4.12',
|
||||
mockito : '2.24.0',
|
||||
mockitoKotlin : '2.1.0',
|
||||
testCore : '1.2.0'
|
||||
testCore : '1.2.0',
|
||||
espresso : '3.2.0',
|
||||
fragmentTest : '1.1.0',
|
||||
robolectric : '4.3'
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue