Add instrumented test to test repository together with room database. Improve data handling on first boot.
Some checks failed
continuous-integration/drone/push Build was killed
Some checks failed
continuous-integration/drone/push Build was killed
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
package me.zobrist.tichucounter
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("me.zobrist.tichucounter", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
package me.zobrist.tichucounter
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.zobrist.tichucounter.data.AppDatabase
|
||||
import me.zobrist.tichucounter.data.GameDao
|
||||
import me.zobrist.tichucounter.data.GameWithScores
|
||||
import me.zobrist.tichucounter.data.RoundDao
|
||||
import me.zobrist.tichucounter.repository.GameRepository
|
||||
import org.junit.After
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RepositoryInstrumentedTest {
|
||||
private lateinit var gameDao: GameDao
|
||||
private lateinit var roundDao: RoundDao
|
||||
private lateinit var repository: GameRepository
|
||||
private lateinit var db: AppDatabase
|
||||
|
||||
@Before
|
||||
fun createDb() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = Room.inMemoryDatabaseBuilder(
|
||||
context, AppDatabase::class.java).build()
|
||||
roundDao = db.roundDao()
|
||||
gameDao = db.gameDao()
|
||||
|
||||
repository = GameRepository(gameDao, roundDao)
|
||||
}
|
||||
|
||||
@After
|
||||
@Throws(IOException::class)
|
||||
fun closeDb() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun gameInitialisation() = runTest {
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
assertEquals("TeamA", it.game.nameA)
|
||||
assertEquals("TeamB", it.game.nameB)
|
||||
assertTrue(it.game.active)
|
||||
assertEquals(0, it.rounds.count())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun modifyNames() = runTest {
|
||||
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
}
|
||||
|
||||
repository.updateActiveTeamName(nameA ="aaa")
|
||||
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
assertEquals("aaa", it.game.nameA)
|
||||
assertEquals("TeamB", it.game.nameB)
|
||||
}
|
||||
|
||||
repository.updateActiveTeamName(nameB ="bbb")
|
||||
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
assertEquals("aaa", it.game.nameA)
|
||||
assertEquals("bbb", it.game.nameB)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun newGame() = runTest {
|
||||
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
}
|
||||
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
|
||||
|
||||
repository.getAllWithRoundFlow().take(1).collect() { it ->
|
||||
assertEquals(6, it.count())
|
||||
|
||||
var uid: Long = 1
|
||||
it.forEach {game ->
|
||||
assertEquals(uid++, game.game.uid)
|
||||
assertEquals(0, game.rounds.count())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun setActive() = runTest {
|
||||
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
}
|
||||
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
|
||||
|
||||
repository.getAllWithRoundFlow().take(1).collect() { it ->
|
||||
val filtered = it.filter { it.game.active }
|
||||
assertEquals(1, filtered.count())
|
||||
assertEquals(6, filtered.first().game.uid)
|
||||
}
|
||||
|
||||
repository.setActive(2)
|
||||
|
||||
repository.getAllWithRoundFlow().take(1).collect() { it ->
|
||||
val filtered = it.filter { it.game.active }
|
||||
assertEquals(1, filtered.count())
|
||||
assertEquals(2, filtered.first().game.uid)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun addRoundToActiveGame() = runTest {
|
||||
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
}
|
||||
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
|
||||
|
||||
repository.addRoundToActiveGame(1,1)
|
||||
repository.addRoundToActiveGame(2,2)
|
||||
repository.addRoundToActiveGame(3,3)
|
||||
repository.addRoundToActiveGame(4,4)
|
||||
repository.addRoundToActiveGame(5,5)
|
||||
repository.addRoundToActiveGame(6,6)
|
||||
|
||||
|
||||
repository.getAllWithRoundFlow().take(1).collect() { it ->
|
||||
val filtered = it.filter { it.rounds.isNotEmpty() }
|
||||
assertEquals(1, filtered.count())
|
||||
assertEquals(6, filtered.first().rounds.count())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun lastRound() = runTest {
|
||||
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
}
|
||||
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
repository.newGame()
|
||||
|
||||
assertNull(repository.getLastRound())
|
||||
|
||||
repository.addRoundToActiveGame(1,1)
|
||||
repository.addRoundToActiveGame(2,2)
|
||||
repository.addRoundToActiveGame(3,3)
|
||||
repository.addRoundToActiveGame(4,4)
|
||||
repository.addRoundToActiveGame(5,5)
|
||||
repository.addRoundToActiveGame(6,6)
|
||||
|
||||
var lastRound = repository.getLastRound()
|
||||
assertEquals(6, lastRound?.scoreA)
|
||||
assertEquals(6, lastRound?.scoreB)
|
||||
|
||||
repository.deleteLastRound()
|
||||
|
||||
lastRound = repository.getLastRound()
|
||||
assertEquals(5, lastRound?.scoreA)
|
||||
assertEquals(5, lastRound?.scoreB)
|
||||
|
||||
repository.deleteLastRound()
|
||||
repository.deleteLastRound()
|
||||
repository.deleteLastRound()
|
||||
repository.deleteLastRound()
|
||||
repository.deleteLastRound()
|
||||
|
||||
assertNull(repository.getLastRound())
|
||||
|
||||
// No error thrown
|
||||
repository.deleteLastRound()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun deleteInactive() = runTest {
|
||||
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
}
|
||||
|
||||
for (i in 1..6) {
|
||||
repository.newGame()
|
||||
repository.addRoundToActiveGame(1,1)
|
||||
repository.addRoundToActiveGame(2,2)
|
||||
repository.addRoundToActiveGame(3,3)
|
||||
repository.addRoundToActiveGame(4,4)
|
||||
repository.addRoundToActiveGame(5,5)
|
||||
repository.addRoundToActiveGame(6,6)
|
||||
}
|
||||
assertEquals(6 * 6, roundDao.getAll().count())
|
||||
|
||||
repository.deleteAllInactive()
|
||||
|
||||
// Consists of two transactions. Delete games then delete rounds.
|
||||
repository.getAllWithRoundFlow().take(1).collect() { it ->
|
||||
assertEquals(1, it.count())
|
||||
assertEquals(6, it.first().rounds.count())
|
||||
}
|
||||
assertEquals(6, roundDao.getAll().count())
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun deleteById() = runTest {
|
||||
|
||||
repository.getActiveGameFlow().take(1).collect {
|
||||
}
|
||||
|
||||
for (i in 1..6) {
|
||||
repository.newGame()
|
||||
repository.addRoundToActiveGame(1,1)
|
||||
repository.addRoundToActiveGame(2,2)
|
||||
repository.addRoundToActiveGame(3,3)
|
||||
repository.addRoundToActiveGame(4,4)
|
||||
repository.addRoundToActiveGame(5,5)
|
||||
repository.addRoundToActiveGame(6,6)
|
||||
}
|
||||
|
||||
// Non existing Id
|
||||
repository.deleteGame(10)
|
||||
|
||||
repository.getAllWithRoundFlow().take(1).collect() { it ->
|
||||
assertEquals(7, it.count())
|
||||
}
|
||||
|
||||
// Non existing Id
|
||||
val toDelete: Long = 3
|
||||
repository.deleteGame(toDelete)
|
||||
|
||||
repository.getAllWithRoundFlow().take(1).collect() { it ->
|
||||
assertEquals(6, it.count())
|
||||
assertEquals(0, it.count { it.game.uid == toDelete })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package me.zobrist.tichucounter.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Update
|
||||
import androidx.room.*
|
||||
|
||||
@Dao
|
||||
interface DaoBase<T> {
|
||||
|
||||
@@ -12,7 +12,7 @@ interface GameDao : DaoBase<Game> {
|
||||
fun getAll(): Flow<List<Game>>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM game where uid ")
|
||||
@Query("SELECT * FROM game")
|
||||
fun getGamesWithRounds(): Flow<List<GameWithScores>>
|
||||
|
||||
@Transaction
|
||||
@@ -20,10 +20,13 @@ interface GameDao : DaoBase<Game> {
|
||||
fun getActiveWithRounds(): Flow<GameWithScores?>
|
||||
|
||||
@Query("SELECT * FROM game WHERE uid is :gameId")
|
||||
fun getGameById(gameId: Long): Flow<Game>
|
||||
fun getGameById(gameId: Long): Game
|
||||
|
||||
@Query("SELECT * FROM game WHERE active is 1")
|
||||
fun getActive(): Flow<Game?>
|
||||
fun getActiveAsFlow(): Flow<Game?>
|
||||
|
||||
@Query("SELECT * FROM game WHERE active is 1")
|
||||
fun getActive(): Game?
|
||||
|
||||
|
||||
@Query("UPDATE game SET active = 1 WHERE uid is :gameId;")
|
||||
|
||||
@@ -5,13 +5,14 @@ import androidx.room.Entity
|
||||
import androidx.room.Relation
|
||||
import me.zobrist.tichucounter.data.entity.Game
|
||||
import me.zobrist.tichucounter.data.entity.Round
|
||||
import java.util.*
|
||||
|
||||
@Entity
|
||||
data class GameWithScores(
|
||||
@Embedded val game: Game,
|
||||
@Embedded val game: Game = Game(),
|
||||
@Relation(
|
||||
parentColumn = "uid",
|
||||
entityColumn = "gameId"
|
||||
)
|
||||
val rounds: List<Round>
|
||||
val rounds: List<Round> = emptyList()
|
||||
)
|
||||
@@ -6,10 +6,10 @@ import java.util.*
|
||||
|
||||
@Entity
|
||||
data class Game(
|
||||
var active: Boolean,
|
||||
var nameA: String,
|
||||
var nameB: String,
|
||||
val created: Date,
|
||||
var modified: Date,
|
||||
var active: Boolean = true,
|
||||
var nameA: String = "TeamA",
|
||||
var nameB: String = "TeamB",
|
||||
val created: Date = Date(),
|
||||
var modified: Date = Date(),
|
||||
@PrimaryKey(autoGenerate = true) override val uid: Long = 0
|
||||
) : IEntity
|
||||
@@ -1,11 +1,7 @@
|
||||
package me.zobrist.tichucounter.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import me.zobrist.tichucounter.data.GameDao
|
||||
import me.zobrist.tichucounter.data.GameWithScores
|
||||
import me.zobrist.tichucounter.data.RoundDao
|
||||
@@ -19,20 +15,15 @@ class GameRepository @Inject constructor(
|
||||
private val roundDao: RoundDao
|
||||
) {
|
||||
|
||||
private var _activeGame: Game? = null
|
||||
|
||||
val activeGame: Game
|
||||
get() {
|
||||
return _activeGame!!
|
||||
}
|
||||
private var activeGame: Game = Game(true, "TeamA", "TeamB", Date(), Date())
|
||||
|
||||
init {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
gameDao.getActive().collect {
|
||||
gameDao.getActiveAsFlow().collect {
|
||||
if (it == null) {
|
||||
gameDao.insert(Game(true, "TeamA", "TeamB", Date(), Date()))
|
||||
newGame()
|
||||
} else {
|
||||
_activeGame = it
|
||||
activeGame = it
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,16 +31,25 @@ class GameRepository @Inject constructor(
|
||||
|
||||
suspend fun newGame() {
|
||||
withContext(Dispatchers.IO) {
|
||||
val id =
|
||||
gameDao.insert(Game(true, activeGame.nameA, activeGame.nameB, Date(), Date()))
|
||||
val id = gameDao.insert(Game(true, activeGame.nameA, activeGame.nameB, Date(), Date()))
|
||||
setActive(id)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateGame(game: Game) {
|
||||
game.modified = Date()
|
||||
suspend fun updateActiveTeamName(nameA: String? = null, nameB: String? = null) {
|
||||
|
||||
val newA = nameA ?: activeGame.nameA
|
||||
val newB = nameB ?: activeGame.nameB
|
||||
|
||||
if(newA == activeGame.nameA && newB == activeGame.nameB) {
|
||||
return
|
||||
}
|
||||
|
||||
activeGame.modified = Date()
|
||||
activeGame.nameA = newA
|
||||
activeGame.nameB = newB
|
||||
withContext(Dispatchers.IO) {
|
||||
gameDao.update(game)
|
||||
gameDao.update(activeGame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,11 +92,10 @@ class GameRepository @Inject constructor(
|
||||
suspend fun deleteGame(uid: Long) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
gameDao.getGameById(uid).take(1).collect {
|
||||
gameDao.delete(it)
|
||||
val rounds = roundDao.getAllForGame(it.uid)
|
||||
roundDao.delete(rounds)
|
||||
}
|
||||
val game = gameDao.getGameById(uid)
|
||||
gameDao.delete(game)
|
||||
val rounds = roundDao.getAllForGame(game.uid)
|
||||
roundDao.delete(rounds)
|
||||
} catch (_: NullPointerException) {
|
||||
}
|
||||
}
|
||||
@@ -107,9 +106,8 @@ class GameRepository @Inject constructor(
|
||||
try {
|
||||
gameDao.getAll().take(1).collect { games ->
|
||||
|
||||
val activeId = games.first { it.active }.uid
|
||||
val gamesToDelete = games.filter { !it.active }
|
||||
val roundsToDelete = roundDao.getAll().filter { it.gameId != activeId }
|
||||
val gamesToDelete = games.filter { it.uid != activeGame.uid }
|
||||
val roundsToDelete = roundDao.getAll().filter { it.gameId != activeGame.uid }
|
||||
|
||||
gameDao.delete(gamesToDelete)
|
||||
roundDao.delete(roundsToDelete)
|
||||
@@ -119,8 +117,8 @@ class GameRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun getActiveGameFlow(): Flow<GameWithScores?> {
|
||||
return gameDao.getActiveWithRounds()
|
||||
fun getActiveGameFlow(): Flow<GameWithScores> {
|
||||
return gameDao.getActiveWithRounds().filter { it != null }.map { it!! }
|
||||
}
|
||||
|
||||
fun getAllWithRoundFlow(): Flow<List<GameWithScores>> {
|
||||
|
||||
@@ -34,17 +34,15 @@ class MainViewModel @Inject constructor(
|
||||
|
||||
gameRepository.getActiveGameFlow().collect {
|
||||
|
||||
activeGameHasRounds = it?.rounds?.isNotEmpty() == true
|
||||
activeGameHasRounds = it.rounds.isNotEmpty() == true
|
||||
|
||||
if (it != null) {
|
||||
isUndoActionActive = it.rounds.isNotEmpty()
|
||||
isUndoActionActive = it.rounds.isNotEmpty()
|
||||
|
||||
if (expectedRoundCount != it.rounds.count()) {
|
||||
redoRounds.clear()
|
||||
}
|
||||
|
||||
expectedRoundCount = it.rounds.count()
|
||||
if (expectedRoundCount != it.rounds.count()) {
|
||||
redoRounds.clear()
|
||||
}
|
||||
|
||||
expectedRoundCount = it.rounds.count()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,17 +245,13 @@ class CounterViewModel @Inject constructor(
|
||||
|
||||
override fun updateNameA(value: String) {
|
||||
viewModelScope.launch {
|
||||
val game = gameRepository.activeGame
|
||||
game.nameA = value
|
||||
gameRepository.updateGame(game)
|
||||
gameRepository.updateActiveTeamName(nameA = value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateNameB(value: String) {
|
||||
viewModelScope.launch {
|
||||
val game = gameRepository.activeGame
|
||||
game.nameB = value
|
||||
gameRepository.updateGame(game)
|
||||
gameRepository.updateActiveTeamName(nameB = value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user