diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
index 36c3f52..2c92f6d 100644
--- a/.idea/androidTestResultsUserPreferences.xml
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -38,6 +38,23 @@
+
+
+
+
+
+
+
@@ -58,7 +75,12 @@
diff --git a/app/src/androidTest/java/com/codenames/frontend/GameboardScreenTest.kt b/app/src/androidTest/java/com/codenames/frontend/GameboardScreenTest.kt
index f4b5bf9..e8d3ed9 100644
--- a/app/src/androidTest/java/com/codenames/frontend/GameboardScreenTest.kt
+++ b/app/src/androidTest/java/com/codenames/frontend/GameboardScreenTest.kt
@@ -48,6 +48,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -71,6 +72,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -91,6 +93,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -122,6 +125,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -156,6 +160,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -179,6 +184,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -205,6 +211,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -236,6 +243,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -258,6 +266,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -279,6 +288,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -300,6 +310,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = {},
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -324,6 +335,7 @@ class GameboardScreenTest {
onReveal = {},
onPassTurn = { passTurnClicks++ },
onCheatRequest = {},
+ onReturnToHome = {},
)
}
@@ -355,6 +367,7 @@ class GameboardScreenTest {
onHintChange = { _, _ -> },
onReveal = { positions -> revealedPositions = positions },
onCheatRequest = {},
+ onReturnToHome = {},
)
}
diff --git a/app/src/androidTest/java/com/codenames/frontend/UserPreferencesDataStoreTest.kt b/app/src/androidTest/java/com/codenames/frontend/UserPreferencesDataStoreTest.kt
index a6308b5..76736b8 100644
--- a/app/src/androidTest/java/com/codenames/frontend/UserPreferencesDataStoreTest.kt
+++ b/app/src/androidTest/java/com/codenames/frontend/UserPreferencesDataStoreTest.kt
@@ -87,4 +87,19 @@ class UserPreferencesDataStoreTest {
assertNull(result.lobbyRole)
assertNull(result.lobbyTeam)
}
+
+ @Test
+ fun removeUserId_removesUserId() =
+ runBlocking {
+ dataStore.saveUserData(
+ username = "User",
+ userId = UUID.randomUUID(),
+ )
+
+ dataStore.removeUserId()
+
+ val result = dataStore.userData.first()
+
+ assertNull(result.userId)
+ }
}
diff --git a/app/src/main/java/com/codenames/frontend/data/datastore/UserPreferencesDataStore.kt b/app/src/main/java/com/codenames/frontend/data/datastore/UserPreferencesDataStore.kt
index c2c8586..d079fab 100644
--- a/app/src/main/java/com/codenames/frontend/data/datastore/UserPreferencesDataStore.kt
+++ b/app/src/main/java/com/codenames/frontend/data/datastore/UserPreferencesDataStore.kt
@@ -80,4 +80,10 @@ class UserPreferencesDataStore
prefs.remove(PreferencesKeys.LOBBY_TEAM)
}
}
+
+ suspend fun removeUserId() {
+ context.dataStore.edit { prefs ->
+ prefs.remove(PreferencesKeys.USER_ID)
+ }
+ }
}
diff --git a/app/src/main/java/com/codenames/frontend/data/repository/GameRepository.kt b/app/src/main/java/com/codenames/frontend/data/repository/GameRepository.kt
index 35e1206..a5db56b 100644
--- a/app/src/main/java/com/codenames/frontend/data/repository/GameRepository.kt
+++ b/app/src/main/java/com/codenames/frontend/data/repository/GameRepository.kt
@@ -1,6 +1,5 @@
package com.codenames.frontend.data.repository
-import com.codenames.frontend.data.model.enums.Role
import com.codenames.frontend.data.model.enums.Team
import com.codenames.frontend.network.dto.CheatCardMessage
import com.codenames.frontend.network.dto.ClueMessageDto
@@ -68,10 +67,8 @@ class GameRepository
username: String,
userId: UUID,
lobbyCode: String,
- lobbyRole: Role,
- lobbyTeam: Team,
) {
- val msg = WebSocketJoinMessage(username, lobbyCode)
+ val msg = WebSocketJoinMessage(username, lobbyCode, userId.toString())
webSocketHandler.sendReconnectMessage(msg)
}
diff --git a/app/src/main/java/com/codenames/frontend/data/repository/LobbyRepository.kt b/app/src/main/java/com/codenames/frontend/data/repository/LobbyRepository.kt
index fd0f9f6..7a6f504 100644
--- a/app/src/main/java/com/codenames/frontend/data/repository/LobbyRepository.kt
+++ b/app/src/main/java/com/codenames/frontend/data/repository/LobbyRepository.kt
@@ -5,6 +5,7 @@ import com.codenames.frontend.data.model.enums.Team
import com.codenames.frontend.network.api.LobbyApi
import com.codenames.frontend.network.dto.LobbyResponse
import com.codenames.frontend.network.dto.PlayerDto
+import java.util.UUID
import javax.inject.Inject
class LobbyRepository
@@ -16,28 +17,32 @@ class LobbyRepository
suspend fun leaveLobby(
lobbyCode: String,
- username: String,
- ): LobbyResponse = api.leaveLobby(lobbyCode, username)
+ userId: UUID,
+ ): LobbyResponse = api.leaveLobby(lobbyCode, userId.toString())
suspend fun joinLobby(
username: String,
lobbyCode: String,
- ): LobbyResponse = api.joinLobby(lobbyCode, username)
+ ): LobbyResponse {
+ val msg = api.joinLobby(lobbyCode, username)
+ return msg
+ }
suspend fun getLobbyInfo(lobbyCode: String): LobbyResponse = api.getLobbyInfo(lobbyCode)
suspend fun changeRole(
username: String,
+ userId: UUID,
lobbyCode: String,
role: Role,
team: Team,
): LobbyResponse {
- val player = PlayerDto(username, role, team, false)
+ val player = PlayerDto(username, role, team, false, userId.toString())
return api.changeRole(lobbyCode, player)
}
suspend fun sendStartGame(
lobbyCode: String,
- username: String,
- ): LobbyResponse = api.startGame(lobbyCode, username)
+ userId: UUID,
+ ): LobbyResponse = api.startGame(lobbyCode, userId.toString())
}
diff --git a/app/src/main/java/com/codenames/frontend/data/repository/SessionRepository.kt b/app/src/main/java/com/codenames/frontend/data/repository/SessionRepository.kt
index a4d00ed..1d3d6e3 100644
--- a/app/src/main/java/com/codenames/frontend/data/repository/SessionRepository.kt
+++ b/app/src/main/java/com/codenames/frontend/data/repository/SessionRepository.kt
@@ -37,4 +37,8 @@ class SessionRepository
suspend fun clearLobbyData() {
dataStore.clearSessionData()
}
+
+ suspend fun clearUserId() {
+ dataStore.removeUserId()
+ }
}
diff --git a/app/src/main/java/com/codenames/frontend/network/api/LobbyApi.kt b/app/src/main/java/com/codenames/frontend/network/api/LobbyApi.kt
index ccfb313..c2f6c4d 100644
--- a/app/src/main/java/com/codenames/frontend/network/api/LobbyApi.kt
+++ b/app/src/main/java/com/codenames/frontend/network/api/LobbyApi.kt
@@ -29,7 +29,7 @@ interface LobbyApi {
@GET("lobby/{lobbyCode}/leave")
suspend fun leaveLobby(
@Path("lobbyCode") lobbyCode: String,
- @Query("username") username: String,
+ @Query("uuid") uuid: String,
): LobbyResponse
@POST("lobby/{lobbyCode}/select-position")
@@ -41,6 +41,6 @@ interface LobbyApi {
@GET("lobby/{lobbyCode}/start-game")
suspend fun startGame(
@Path("lobbyCode") lobbyCode: String,
- @Query("username") username: String,
+ @Query("uuid") uuid: String,
): LobbyResponse
}
diff --git a/app/src/main/java/com/codenames/frontend/network/dto/LobbyResponse.kt b/app/src/main/java/com/codenames/frontend/network/dto/LobbyResponse.kt
index 09238e5..98f1439 100644
--- a/app/src/main/java/com/codenames/frontend/network/dto/LobbyResponse.kt
+++ b/app/src/main/java/com/codenames/frontend/network/dto/LobbyResponse.kt
@@ -8,4 +8,5 @@ data class LobbyResponse(
val playerList: List? = emptyList(),
val isStarted: Boolean = false,
val message: String = "",
+ val uuid: String? = null,
)
diff --git a/app/src/main/java/com/codenames/frontend/network/dto/PlayerDto.kt b/app/src/main/java/com/codenames/frontend/network/dto/PlayerDto.kt
index c663fa7..602a23d 100644
--- a/app/src/main/java/com/codenames/frontend/network/dto/PlayerDto.kt
+++ b/app/src/main/java/com/codenames/frontend/network/dto/PlayerDto.kt
@@ -10,4 +10,5 @@ data class PlayerDto(
val role: Role? = null,
val team: Team? = null,
val isHost: Boolean,
+ val uuid: String? = null,
)
diff --git a/app/src/main/java/com/codenames/frontend/network/dto/WebSocketJoinMessage.kt b/app/src/main/java/com/codenames/frontend/network/dto/WebSocketJoinMessage.kt
index 2423f49..abef119 100644
--- a/app/src/main/java/com/codenames/frontend/network/dto/WebSocketJoinMessage.kt
+++ b/app/src/main/java/com/codenames/frontend/network/dto/WebSocketJoinMessage.kt
@@ -9,4 +9,5 @@ data class WebSocketJoinMessage(
val username: String,
@SerialName("code")
val lobbyCode: String,
+ val uuid: String,
)
diff --git a/app/src/main/java/com/codenames/frontend/network/websocket/GameWebSocketController.kt b/app/src/main/java/com/codenames/frontend/network/websocket/GameWebSocketController.kt
index f131a2d..5682616 100644
--- a/app/src/main/java/com/codenames/frontend/network/websocket/GameWebSocketController.kt
+++ b/app/src/main/java/com/codenames/frontend/network/websocket/GameWebSocketController.kt
@@ -23,6 +23,10 @@ class GameWebSocketController
webSocketSessionManager.connectStomp()
}
+ suspend fun disconnect() {
+ webSocketSessionManager.disconnect()
+ }
+
suspend fun startGame(msg: StartGameMessage) {
webSocketSessionManager.getSession().convertAndSend("/app/start-game", msg, StartGameMessage.serializer())
}
diff --git a/app/src/main/java/com/codenames/frontend/ui/screens/GameScreenWrapper.kt b/app/src/main/java/com/codenames/frontend/ui/screens/GameScreenWrapper.kt
index a9c6d17..6464f29 100644
--- a/app/src/main/java/com/codenames/frontend/ui/screens/GameScreenWrapper.kt
+++ b/app/src/main/java/com/codenames/frontend/ui/screens/GameScreenWrapper.kt
@@ -75,5 +75,10 @@ fun GameScreenWrapper(
onSettingsClick = {
navController.navigate(Screen.Settings.route)
},
+ onReturnToHome = {
+ navController.navigate(Screen.Start.route)
+ lobbyViewModel.cleanup()
+ gameViewModel.resetGameState()
+ },
)
}
diff --git a/app/src/main/java/com/codenames/frontend/ui/screens/GameboardScreen.kt b/app/src/main/java/com/codenames/frontend/ui/screens/GameboardScreen.kt
index 1ddabdb..4767696 100644
--- a/app/src/main/java/com/codenames/frontend/ui/screens/GameboardScreen.kt
+++ b/app/src/main/java/com/codenames/frontend/ui/screens/GameboardScreen.kt
@@ -132,6 +132,7 @@ fun GameboardScreen(
onPassTurn: () -> Unit = {},
onSendChatMessage: (ChatTab, String) -> Unit = { _, _ -> },
onSettingsClick: (() -> Unit)? = null,
+ onReturnToHome: (() -> Unit),
) {
val dimensions = LocalResponsiveDimensions.current
@@ -157,7 +158,6 @@ fun GameboardScreen(
userRole == PlayerRoles.BLUE_SPYMASTER || userRole == PlayerRoles.RED_SPYMASTER
val isActiveSpymaster = userRole == currentTurn && isSpymaster
val canEndTurn = userRole == currentTurn && !isSpymaster && remainingGuesses > 0
- val canSelectCards = canEndTurn
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
@@ -172,9 +172,13 @@ fun GameboardScreen(
val onInputChange: (String) -> Unit = { hintInput = it }
- val currentCanSelectCards by rememberUpdatedState(canSelectCards)
+ val currentCanSelectCards by rememberUpdatedState(canEndTurn)
val currentSelectedCardPositions by rememberUpdatedState(selectedCardPositions)
+ val gameOver = gameState.winner != null
+
+ val backgroundTeam = if (winner != null) getPlayerRoleFromTeam(winner) else currentTurn
+
val shakeDetector =
remember {
ShakeDetector {
@@ -206,7 +210,7 @@ fun GameboardScreen(
modifier =
modifier
.fillMaxSize()
- .background(getTeamBackgroundColor(currentTurn)),
+ .background(getTeamBackgroundColor(backgroundTeam)),
) {
Column(
modifier =
@@ -257,7 +261,7 @@ fun GameboardScreen(
),
selectionState =
BoardSelectionState(
- canSelectCards = canSelectCards,
+ canSelectCards = canEndTurn,
selectedCardPositions = selectedCardPositions,
remainingGuesses = remainingGuesses,
),
@@ -277,18 +281,19 @@ fun GameboardScreen(
.weight(1f)
.fillMaxWidth(),
)
-
- HintSection(
- isActiveSpymaster,
- currentHint,
- hintInput,
- countInput,
- onHintChange = onHintChange,
- onInputChange,
- onCountChange = { countInput = it },
- keyboardController,
- focusManager,
- )
+ if (!gameOver) {
+ HintSection(
+ isActiveSpymaster,
+ currentHint,
+ hintInput,
+ countInput,
+ onHintChange = onHintChange,
+ onInputChange,
+ onCountChange = { countInput = it },
+ keyboardController,
+ focusManager,
+ )
+ }
}
GameChatOverlay(
@@ -315,7 +320,7 @@ fun GameboardScreen(
)
DeselectAllButton(
- isVisible = canSelectCards && selectedCardPositions.isNotEmpty(),
+ isVisible = canEndTurn && selectedCardPositions.isNotEmpty(),
onClick = { selectedCardPositions = emptyList() },
modifier =
Modifier
@@ -327,6 +332,22 @@ fun GameboardScreen(
),
)
+ if (gameOver) {
+ AppButton(
+ onClick = onReturnToHome,
+ text = "Return to home screen",
+ modifier =
+ Modifier
+ .align(Alignment.BottomCenter)
+ .padding(
+ start = dimensions.screenPadding,
+ end = dimensions.screenPadding,
+ bottom = dimensions.screenPadding,
+ ),
+ style = AppButtonStyle(backgroundBrush = greenGradient),
+ )
+ }
+
onSettingsClick?.let { openSettings ->
SettingsCornerButton(
onClick = openSettings,
@@ -1095,3 +1116,9 @@ fun getTeamBackgroundColor(currentTurn: PlayerRoles): Color =
PlayerRoles.RED_OPERATIVE, PlayerRoles.RED_SPYMASTER -> AppRedTeamBackground
PlayerRoles.NONE -> AppBackground
}
+
+fun getPlayerRoleFromTeam(color: Team): PlayerRoles =
+ when (color) {
+ Team.RED -> PlayerRoles.RED_SPYMASTER
+ Team.BLUE -> PlayerRoles.BLUE_SPYMASTER
+ }
diff --git a/app/src/main/java/com/codenames/frontend/ui/screens/JoinLobbyScreen.kt b/app/src/main/java/com/codenames/frontend/ui/screens/JoinLobbyScreen.kt
index 290112a..863b2c6 100644
--- a/app/src/main/java/com/codenames/frontend/ui/screens/JoinLobbyScreen.kt
+++ b/app/src/main/java/com/codenames/frontend/ui/screens/JoinLobbyScreen.kt
@@ -76,7 +76,7 @@ fun JoinlobbyScreen(
val focusManager = LocalFocusManager.current
val state by viewModel.state.collectAsState()
- val username by sessionViewModel.userState.collectAsState()
+ val userState by sessionViewModel.userState.collectAsState()
val joinEnabled = isLobbyIdValid(lobbyId)
@@ -92,7 +92,7 @@ fun JoinlobbyScreen(
keyboardController?.hide()
focusManager.clearFocus()
- viewModel.joinLobby(username.username, lobbyId)
+ viewModel.joinLobby(userState.username, lobbyId)
}
Box(
diff --git a/app/src/main/java/com/codenames/frontend/ui/screens/LobbyScreen.kt b/app/src/main/java/com/codenames/frontend/ui/screens/LobbyScreen.kt
index a4db508..ca537a9 100644
--- a/app/src/main/java/com/codenames/frontend/ui/screens/LobbyScreen.kt
+++ b/app/src/main/java/com/codenames/frontend/ui/screens/LobbyScreen.kt
@@ -40,7 +40,6 @@ import com.codenames.frontend.ui.roles.PlayerRoles
import com.codenames.frontend.ui.theme.AppBackground
import com.codenames.frontend.ui.theme.AppBlack
import com.codenames.frontend.ui.theme.AppBlueLight
-import com.codenames.frontend.ui.theme.AppMutedDark
import com.codenames.frontend.ui.theme.AppRedLight
import com.codenames.frontend.ui.theme.AppWhite
import com.codenames.frontend.ui.theme.LocalResponsiveDimensions
@@ -74,7 +73,7 @@ fun LobbyScreen(
val connectionState by gameViewModel.connectionState.collectAsState()
val onStartGame = {
- viewModel.sendStartGame(userState.username)
+ viewModel.sendStartGame(userState.username, userState.userId)
}
LaunchedEffect(userState.userId) {
@@ -150,7 +149,7 @@ fun LobbyScreen(
gradient = blueGradient,
textColor = AppBlueLight,
title = "BLUE TEAM",
- onRoleSelect = { viewModel.changeRole(it, userState.username) },
+ onRoleSelect = { viewModel.changeRole(it, userState.username, userState.userId) },
lobbyUiState = lobbyUiState,
)
@@ -173,7 +172,7 @@ fun LobbyScreen(
gradient = redGradient,
textColor = AppRedLight,
title = "RED TEAM",
- onRoleSelect = { viewModel.changeRole(it, userState.username) },
+ onRoleSelect = { viewModel.changeRole(it, userState.username, userState.userId) },
lobbyUiState = lobbyUiState,
)
}
@@ -351,25 +350,13 @@ fun GameSettingsColumn(
text = "GAME SETTINGS",
color = AppWhite,
fontWeight = FontWeight.Bold,
- fontSize = dimensions.smallFontSize,
+ fontSize = dimensions.buttonFontSize,
modifier = Modifier.padding(bottom = dimensions.itemSpacing),
)
- AppButton(
- text = "TIMER: OFF",
- onClick = { /* TODO: Timer Logik */ },
- modifier =
- Modifier
- .fillMaxWidth()
- .height(dimensions.secondaryButtonHeight)
- .padding(bottom = dimensions.smallSpacing),
- style =
- AppButtonStyle(
- containerColor = AppMutedDark,
- contentColor = AppWhite,
- fontSize = dimensions.smallFontSize,
- lineHeight = dimensions.bodyFontSize,
- ),
+ Text(
+ text = "Upgrade to pro to see all features!",
+ color = AppWhite,
)
}
@@ -409,7 +396,7 @@ fun GameSettingsColumn(
}
}
}
- viewModel.leaveLobby(username = userState.username, onResult = onResult)
+ viewModel.leaveLobby(userId = userState.userId, onResult = onResult)
},
modifier =
Modifier
diff --git a/app/src/main/java/com/codenames/frontend/ui/screens/UserNameScreen.kt b/app/src/main/java/com/codenames/frontend/ui/screens/UserNameScreen.kt
index fa6e5cd..a2bd72d 100644
--- a/app/src/main/java/com/codenames/frontend/ui/screens/UserNameScreen.kt
+++ b/app/src/main/java/com/codenames/frontend/ui/screens/UserNameScreen.kt
@@ -38,7 +38,6 @@ import com.codenames.frontend.ui.theme.blueGradient
import com.codenames.frontend.viewmodel.GameViewModel
import com.codenames.frontend.viewmodel.LobbyViewModel
import com.codenames.frontend.viewmodel.SessionViewModel
-import java.util.UUID
@Suppress("ktlint:standard:function-naming")
@Composable
@@ -117,7 +116,6 @@ fun UserNameScreen(
onClick = {
if (username.isBlank()) return@AppButton
viewModel.setUsername(username)
- viewModel.setUserId(UUID.randomUUID()) // ONLY FOR TESTING!!!!
navController.navigate(Screen.Start.route)
},
modifier =
diff --git a/app/src/main/java/com/codenames/frontend/viewmodel/GameViewModel.kt b/app/src/main/java/com/codenames/frontend/viewmodel/GameViewModel.kt
index 4fe495e..e390fc2 100644
--- a/app/src/main/java/com/codenames/frontend/viewmodel/GameViewModel.kt
+++ b/app/src/main/java/com/codenames/frontend/viewmodel/GameViewModel.kt
@@ -205,7 +205,7 @@ class GameViewModel
if (lobbyCode != null && team != null && role != null) {
viewModelScope.launch {
try {
- gameRepository.sendRejoin(username, userId, lobbyCode, role, team)
+ gameRepository.sendRejoin(username, userId, lobbyCode)
} catch (e: Exception) {
setConnectionError(e)
}
@@ -232,6 +232,15 @@ class GameViewModel
}
}
+ fun resetGameState() {
+ viewModelScope.launch {
+ _uiState.update {
+ GameState()
+ }
+ handler.disconnect()
+ }
+ }
+
private fun setConnectionError(error: Throwable) {
_connectionState.value = ConnectionState.Error(ErrorMessageMapper.toUserMessage(error))
}
diff --git a/app/src/main/java/com/codenames/frontend/viewmodel/LobbyViewModel.kt b/app/src/main/java/com/codenames/frontend/viewmodel/LobbyViewModel.kt
index 9e73915..643b186 100644
--- a/app/src/main/java/com/codenames/frontend/viewmodel/LobbyViewModel.kt
+++ b/app/src/main/java/com/codenames/frontend/viewmodel/LobbyViewModel.kt
@@ -11,6 +11,7 @@ import com.codenames.frontend.data.model.enums.Role
import com.codenames.frontend.data.model.enums.Team
import com.codenames.frontend.data.model.toLobbyState
import com.codenames.frontend.data.repository.LobbyRepository
+import com.codenames.frontend.data.repository.SessionRepository
import com.codenames.frontend.ui.roles.PlayerRoles
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
@@ -20,13 +21,18 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
+import java.util.UUID
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+
+private const val ID_NOT_FOUND = "No valid ID found."
@HiltViewModel
class LobbyViewModel
@Inject
constructor(
private val repository: LobbyRepository,
+ private val sessionRepository: SessionRepository,
) : ViewModel() {
private val _state = MutableStateFlow(LobbyUiState())
val state: StateFlow = _state
@@ -42,13 +48,13 @@ class LobbyViewModel
}
viewModelScope.launch {
setLoading(true)
-
+ sessionRepository.clearUserId()
try {
val response = repository.createLobby(username)
-
_state.update {
response.toLobbyState()
}
+ if (response.uuid != null) sessionRepository.saveUser(username, UUID.fromString(response.uuid))
startPolling(response.lobbyCode)
} catch (e: Exception) {
setError(e)
@@ -71,12 +77,15 @@ class LobbyViewModel
viewModelScope.launch {
setLoading(true)
+ sessionRepository.clearUserId()
+
try {
val response = repository.joinLobby(username, lobbyCode)
_state.update {
response.toLobbyState()
}
+ if (response.uuid != null) sessionRepository.saveUser(username, UUID.fromString(response.uuid))
updateUiState(_state.value.players)
startPolling(response.lobbyCode)
} catch (e: Exception) {
@@ -88,7 +97,7 @@ class LobbyViewModel
}
fun leaveLobby(
- username: String,
+ userId: UUID?,
onResult: (Boolean) -> Unit,
) {
val lobbyCode = _state.value.lobbyCode
@@ -99,12 +108,16 @@ class LobbyViewModel
onResult(successful)
return
}
+ if (userId == null) {
+ setError(ID_NOT_FOUND)
+ return
+ }
viewModelScope.launch {
setLoading(true)
try {
- val response = repository.leaveLobby(lobbyCode, username)
+ val response = repository.leaveLobby(lobbyCode, userId)
_state.update {
response.toLobbyState()
}
@@ -126,18 +139,23 @@ class LobbyViewModel
role: Role,
team: Team,
username: String,
+ userId: UUID?,
) {
val lobbyCode = _state.value.lobbyCode
if (lobbyCode.isNullOrBlank()) {
setError("Not in a Lobby")
return
}
+ if (userId == null) {
+ setError(ID_NOT_FOUND)
+ return
+ }
viewModelScope.launch {
setLoading(true)
try {
- val response = repository.changeRole(username, lobbyCode, role, team)
+ val response = repository.changeRole(username, userId, lobbyCode, role, team)
_state.update {
response.toLobbyState()
@@ -154,12 +172,13 @@ class LobbyViewModel
fun changeRole(
role: PlayerRoles,
username: String,
+ userId: UUID?,
) {
when (role) {
- PlayerRoles.BLUE_SPYMASTER -> changeRole(role = Role.SPYMASTER, team = Team.BLUE, username = username)
- PlayerRoles.RED_SPYMASTER -> changeRole(role = Role.SPYMASTER, team = Team.RED, username = username)
- PlayerRoles.BLUE_OPERATIVE -> changeRole(role = Role.OPERATIVE, team = Team.BLUE, username = username)
- PlayerRoles.RED_OPERATIVE -> changeRole(role = Role.OPERATIVE, team = Team.RED, username = username)
+ PlayerRoles.BLUE_SPYMASTER -> changeRole(role = Role.SPYMASTER, team = Team.BLUE, username = username, userId)
+ PlayerRoles.RED_SPYMASTER -> changeRole(role = Role.SPYMASTER, team = Team.RED, username = username, userId)
+ PlayerRoles.BLUE_OPERATIVE -> changeRole(role = Role.OPERATIVE, team = Team.BLUE, username = username, userId)
+ PlayerRoles.RED_OPERATIVE -> changeRole(role = Role.OPERATIVE, team = Team.RED, username = username, userId)
else -> setError("Invalid role")
}
}
@@ -201,13 +220,20 @@ class LobbyViewModel
return player.isHost
}
- fun sendStartGame(username: String) {
+ fun sendStartGame(
+ username: String,
+ userId: UUID?,
+ ) {
val lobbyCode = _state.value.lobbyCode.orEmpty()
- if (!username.isBlank() && !lobbyCode.isEmpty() && getIsHost(username)) {
+ if (userId == null) {
+ setError(ID_NOT_FOUND)
+ return
+ }
+ if (!username.isBlank() && lobbyCode.isNotEmpty() && getIsHost(username)) {
viewModelScope.launch {
try {
setLoading(true)
- val response = repository.sendStartGame(lobbyCode, username)
+ val response = repository.sendStartGame(lobbyCode, userId)
_state.update {
response.toLobbyState()
}
@@ -232,7 +258,7 @@ class LobbyViewModel
}
}
- private fun cleanup() {
+ fun cleanup() {
_state.update {
it.copy(
lobbyCode = null,
@@ -243,6 +269,8 @@ class LobbyViewModel
redSpymasters = emptyList(),
)
}
+ stopPolling()
+ clearError()
}
private fun updateUiState(players: List) {
@@ -286,7 +314,7 @@ class LobbyViewModel
return@launch
}
- delay(pollingTime)
+ delay(pollingTime.milliseconds)
}
}
}
diff --git a/app/src/main/java/com/codenames/frontend/viewmodel/SessionViewModel.kt b/app/src/main/java/com/codenames/frontend/viewmodel/SessionViewModel.kt
index 9b72566..4e3a923 100644
--- a/app/src/main/java/com/codenames/frontend/viewmodel/SessionViewModel.kt
+++ b/app/src/main/java/com/codenames/frontend/viewmodel/SessionViewModel.kt
@@ -40,14 +40,6 @@ class SessionViewModel
}
}
- fun setUserId(userId: UUID) {
- _userState.update {
- it.copy(
- userId = userId,
- )
- }
- }
-
fun persistLobbyState(
lobbyCode: String,
role: Role,
@@ -71,6 +63,15 @@ class SessionViewModel
}
}
+ fun persistUserId(userId: UUID) {
+ viewModelScope.launch {
+ sessionRepository.saveUser(
+ _userState.value.username,
+ userId,
+ )
+ }
+ }
+
fun clearLobby() {
viewModelScope.launch {
sessionRepository.clearLobbyData()
diff --git a/app/src/test/java/com/codenames/frontend/data/repository/GameRepositoryTest.kt b/app/src/test/java/com/codenames/frontend/data/repository/GameRepositoryTest.kt
index 018eace..06afd6d 100644
--- a/app/src/test/java/com/codenames/frontend/data/repository/GameRepositoryTest.kt
+++ b/app/src/test/java/com/codenames/frontend/data/repository/GameRepositoryTest.kt
@@ -11,6 +11,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import java.util.UUID
@OptIn(ExperimentalCoroutinesApi::class)
class GameRepositoryTest {
@@ -96,4 +97,19 @@ class GameRepositoryTest {
coVerify { webSocketHandler.passTurn(any()) }
}
+
+ @Test
+ fun testSendRejoin() =
+ runTest {
+ val username = "User"
+ val lobbyCode = "ABCDE"
+ val userId = UUID.randomUUID()
+ coEvery { webSocketHandler.sendReconnectMessage(any()) } just Runs
+
+ gameRepository.sendRejoin(username, userId, lobbyCode)
+
+ coVerify {
+ webSocketHandler.sendReconnectMessage(any())
+ }
+ }
}
diff --git a/app/src/test/java/com/codenames/frontend/data/repository/LobbyRepositoryTest.kt b/app/src/test/java/com/codenames/frontend/data/repository/LobbyRepositoryTest.kt
index 0b215ae..a259e43 100644
--- a/app/src/test/java/com/codenames/frontend/data/repository/LobbyRepositoryTest.kt
+++ b/app/src/test/java/com/codenames/frontend/data/repository/LobbyRepositoryTest.kt
@@ -13,6 +13,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
+import java.util.UUID
import kotlin.test.assertFailsWith
@OptIn(ExperimentalCoroutinesApi::class)
@@ -66,14 +67,15 @@ class LobbyRepositoryTest {
runTest {
val username = "Max"
val lobbyCode = "1234"
+ val userId = UUID.randomUUID()
val response = LobbyResponse(lobbyCode, emptyList(), false)
- coEvery { api.leaveLobby(username, lobbyCode) } returns response
+ coEvery { api.leaveLobby(lobbyCode = lobbyCode, uuid = userId.toString()) } returns response
- val result = repository.leaveLobby(username, lobbyCode)
+ val result = repository.leaveLobby(lobbyCode, userId)
- coVerify { api.leaveLobby(username, lobbyCode) }
+ coVerify { api.leaveLobby(lobbyCode, userId.toString()) }
assertEquals(response, result)
}
@@ -97,12 +99,13 @@ class LobbyRepositoryTest {
val lobbyCode = "1234"
val role = Role.OPERATIVE
val team = Team.RED
+ val userId = UUID.randomUUID()
val response = LobbyResponse(lobbyCode, emptyList(), false)
coEvery { api.changeRole(eq(lobbyCode), any()) } returns response
- repository.changeRole(username, lobbyCode, role, team)
+ repository.changeRole(username, userId, lobbyCode, role, team)
coVerify {
api.changeRole(
@@ -133,6 +136,7 @@ class LobbyRepositoryTest {
runTest {
val lobbyCode = "ABCDE"
val username = "Test"
+ val userId = UUID.randomUUID()
val list =
listOf(
PlayerDto(
@@ -145,7 +149,7 @@ class LobbyRepositoryTest {
coEvery { api.startGame(any(), any()) } returns LobbyResponse(lobbyCode, list, false)
- val response = repository.sendStartGame(lobbyCode, username)
+ val response = repository.sendStartGame(lobbyCode, userId)
coVerify { api.startGame(any(), any()) }
assertEquals(response.lobbyCode, lobbyCode)
diff --git a/app/src/test/java/com/codenames/frontend/data/repository/SessionRepositoryTest.kt b/app/src/test/java/com/codenames/frontend/data/repository/SessionRepositoryTest.kt
index 60d11d4..400d218 100644
--- a/app/src/test/java/com/codenames/frontend/data/repository/SessionRepositoryTest.kt
+++ b/app/src/test/java/com/codenames/frontend/data/repository/SessionRepositoryTest.kt
@@ -64,4 +64,14 @@ class SessionRepositoryTest {
dataStore.clearSessionData()
}
}
+
+ @Test
+ fun `clearUserId delegates to datastore`() =
+ runTest {
+ repository.clearUserId()
+
+ coVerify {
+ dataStore.removeUserId()
+ }
+ }
}
diff --git a/app/src/test/java/com/codenames/frontend/network/websocket/GameWebSocketControllerTest.kt b/app/src/test/java/com/codenames/frontend/network/websocket/GameWebSocketControllerTest.kt
index 2038626..56173a3 100644
--- a/app/src/test/java/com/codenames/frontend/network/websocket/GameWebSocketControllerTest.kt
+++ b/app/src/test/java/com/codenames/frontend/network/websocket/GameWebSocketControllerTest.kt
@@ -18,6 +18,7 @@ import org.hildan.krossbow.stomp.conversions.kxserialization.convertAndSend
import org.hildan.krossbow.stomp.conversions.kxserialization.subscribe
import org.junit.Before
import org.junit.Test
+import java.util.UUID
class GameWebSocketControllerTest {
private lateinit var wsClient: GameWebSocketController
@@ -63,7 +64,7 @@ class GameWebSocketControllerTest {
@Test
fun testSendReconnectMessageSendsMessage() =
runTest {
- val msg = WebSocketJoinMessage("name", "1234")
+ val msg = WebSocketJoinMessage("name", "1234", UUID.randomUUID().toString())
wsClient.sendReconnectMessage(msg)
diff --git a/app/src/test/java/com/codenames/frontend/viewmodel/GameViewModelTest.kt b/app/src/test/java/com/codenames/frontend/viewmodel/GameViewModelTest.kt
index 927887a..b94cb4f 100644
--- a/app/src/test/java/com/codenames/frontend/viewmodel/GameViewModelTest.kt
+++ b/app/src/test/java/com/codenames/frontend/viewmodel/GameViewModelTest.kt
@@ -568,7 +568,7 @@ class GameViewModelTest {
fun testSendRejoinMessage_sendsMessage() =
runTest {
coEvery {
- gameRepository.sendRejoin(any(), any(), any(), any(), any())
+ gameRepository.sendRejoin(any(), any(), any())
} just Runs
val rejoinState = RejoinState.Available(SessionState(lobbyCode, Role.OPERATIVE, Team.RED))
@@ -578,7 +578,7 @@ class GameViewModelTest {
advanceUntilIdle()
coVerify {
- gameRepository.sendRejoin(any(), any(), any(), any(), any())
+ gameRepository.sendRejoin(any(), any(), any())
}
}
@@ -586,7 +586,7 @@ class GameViewModelTest {
fun testSendRejoinMessage_ignoresWithoutRejoinState() =
runTest {
coEvery {
- gameRepository.sendRejoin(any(), any(), any(), any(), any())
+ gameRepository.sendRejoin(any(), any(), any())
} just Runs
val rejoinState = null
@@ -596,7 +596,7 @@ class GameViewModelTest {
advanceUntilIdle()
coVerify(exactly = 0) {
- gameRepository.sendRejoin(any(), any(), any(), any(), any())
+ gameRepository.sendRejoin(any(), any(), any())
}
}
@@ -604,7 +604,7 @@ class GameViewModelTest {
fun testSendRejoinMessage_ignoresNullValuesInRejoinState() =
runTest {
coEvery {
- gameRepository.sendRejoin(any(), any(), any(), any(), any())
+ gameRepository.sendRejoin(any(), any(), any())
} just Runs
val rejoinState = RejoinState.Available(SessionState(null, null, null))
@@ -614,7 +614,7 @@ class GameViewModelTest {
advanceUntilIdle()
coVerify(exactly = 0) {
- gameRepository.sendRejoin(any(), any(), any(), any(), any())
+ gameRepository.sendRejoin(any(), any(), any())
}
}
@@ -655,6 +655,29 @@ class GameViewModelTest {
assertEquals(ConnectionState.IDLE, viewModel.connectionState.value)
}
+ @Test
+ fun resetGameState_resetsGameState() =
+ runTest {
+ mockkStatic(Log::class)
+ every { Log.d(any(), any()) } returns 0
+
+ coEvery { client.disconnect() } just Runs
+
+ viewModel.handleMessage(testMessage)
+
+ advanceUntilIdle()
+
+ viewModel.resetGameState()
+
+ advanceUntilIdle()
+
+ val compareMessage = GameState()
+
+ assertEquals(compareMessage.cards, viewModel.uiState.value.cards)
+ assertEquals(compareMessage.winner, viewModel.uiState.value.winner)
+ assertEquals(compareMessage.currentTurn, viewModel.uiState.value.currentTurn)
+ }
+
@Test
fun requestCheat_callsRepository() =
runTest {
diff --git a/app/src/test/java/com/codenames/frontend/viewmodel/LobbyViewModelTest.kt b/app/src/test/java/com/codenames/frontend/viewmodel/LobbyViewModelTest.kt
index 8e428f7..52daa2f 100644
--- a/app/src/test/java/com/codenames/frontend/viewmodel/LobbyViewModelTest.kt
+++ b/app/src/test/java/com/codenames/frontend/viewmodel/LobbyViewModelTest.kt
@@ -5,12 +5,15 @@ import com.codenames.frontend.data.model.enums.ChatTab
import com.codenames.frontend.data.model.enums.Role
import com.codenames.frontend.data.model.enums.Team
import com.codenames.frontend.data.repository.LobbyRepository
+import com.codenames.frontend.data.repository.SessionRepository
import com.codenames.frontend.network.dto.LobbyResponse
import com.codenames.frontend.network.dto.PlayerDto
import com.codenames.frontend.ui.roles.PlayerRoles
+import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
+import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
@@ -30,10 +33,15 @@ import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
+import java.util.UUID
+import kotlin.test.assertNotNull
+import kotlin.time.Duration.Companion.milliseconds
@OptIn(ExperimentalCoroutinesApi::class)
class LobbyViewModelTest {
private val dispatcher = StandardTestDispatcher()
+ val repository = mockk()
+ val sessionRepository = mockk()
@Before
fun setup() {
@@ -48,8 +56,6 @@ class LobbyViewModelTest {
@Test
fun testCreateLobby_success() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "1234",
@@ -59,12 +65,13 @@ class LobbyViewModelTest {
coEvery { repository.createLobby("User") } returns response
coEvery { repository.getLobbyInfo(any()) } returns response
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.createLobby("User")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -78,15 +85,14 @@ class LobbyViewModelTest {
@Test
fun testCreateLobby_error() =
runTest {
- val repository = mockk()
-
coEvery { repository.createLobby(any()) } throws RuntimeException("Network error")
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.createLobby("Max")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -99,8 +105,6 @@ class LobbyViewModelTest {
@Test
fun testJoinLobby_success() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "1234",
@@ -116,11 +120,13 @@ class LobbyViewModelTest {
false,
)
- val viewModel = LobbyViewModel(repository)
+ coEvery { sessionRepository.clearUserId() } just Runs
+
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("User", "1234")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -134,15 +140,14 @@ class LobbyViewModelTest {
@Test
fun testJoinLobby_error() =
runTest {
- val repository = mockk()
-
coEvery { repository.joinLobby(any(), any()) } throws RuntimeException("Network error")
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Max", "1234")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -155,8 +160,6 @@ class LobbyViewModelTest {
@Test
fun testLeaveLobby_success() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "1234",
@@ -171,17 +174,20 @@ class LobbyViewModelTest {
false,
)
+ val userId = UUID.randomUUID()
+
coEvery { repository.joinLobby("User", "1234") } returns response
coEvery { repository.getLobbyInfo(any()) } returns response
- coEvery { repository.leaveLobby("1234", "User") } returns response2
+ coEvery { repository.leaveLobby("1234", userId) } returns response2
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("User", "1234")
- advanceTimeBy(2000)
- viewModel.leaveLobby("User", onResult = {})
+ advanceTimeBy(2000.milliseconds)
+ viewModel.leaveLobby(userId = userId, onResult = {})
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -195,27 +201,27 @@ class LobbyViewModelTest {
@Test
fun testLeaveLobby_error() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "1234",
playerList = listOf(PlayerDto("User", null, Team.RED, true)),
false,
)
+ val userID = UUID.randomUUID()
coEvery { repository.joinLobby("User", "1234") } returns response
coEvery { repository.leaveLobby(any(), any()) } throws RuntimeException("Network error")
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("User", "1234")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
- viewModel.leaveLobby("User", onResult = {})
+ viewModel.leaveLobby(userId = userID, onResult = {})
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -228,10 +234,9 @@ class LobbyViewModelTest {
@Test
fun testChangeRole_success() =
runTest {
- val repository = mockk()
-
val newRole = Role.OPERATIVE
val newTeam = Team.RED
+ val username = "User"
val response =
LobbyResponse(
@@ -246,21 +251,23 @@ class LobbyViewModelTest {
playerList = listOf(PlayerDto("User", newRole, newTeam, true)),
false,
)
+ val userId = UUID.randomUUID()
coEvery { repository.joinLobby("User", "1234") } returns response
coEvery { repository.getLobbyInfo(any()) } returns response
- coEvery { repository.changeRole("User", "1234", newRole, newTeam) } returns response2
+ coEvery { repository.changeRole("User", userId, "1234", newRole, newTeam) } returns response2
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("User", "1234")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
- viewModel.changeRole(newRole, newTeam, "User")
+ viewModel.changeRole(newRole, newTeam, username, userId)
viewModel.stopPollingForTest()
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
val state = viewModel.state.value
@@ -274,8 +281,6 @@ class LobbyViewModelTest {
@Test
fun testChangeRole_error() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "1234",
@@ -290,18 +295,21 @@ class LobbyViewModelTest {
any(),
any(),
any(),
+ any(),
)
} throws RuntimeException("Network error")
- val viewModel = LobbyViewModel(repository)
+ coEvery { sessionRepository.clearUserId() } just Runs
+
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("User", "1234")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
- viewModel.changeRole(Role.OPERATIVE, Team.RED, "User")
+ viewModel.changeRole(Role.OPERATIVE, Team.RED, "User", UUID.randomUUID())
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -314,15 +322,14 @@ class LobbyViewModelTest {
@Test
fun testSetError_WithEmptyValue() =
runTest {
- val repository = mockk()
-
coEvery { repository.createLobby(any()) } throws RuntimeException("")
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.createLobby("Max")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -335,15 +342,14 @@ class LobbyViewModelTest {
@Test
fun testSetError_WithNullValue() =
runTest {
- val repository = mockk()
-
coEvery { repository.createLobby(any()) } throws RuntimeException()
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.createLobby("Max")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -357,8 +363,6 @@ class LobbyViewModelTest {
@Test
fun testPolling_callsRepositoryRepeatedly() =
runTest {
- val repository = mockk()
-
coEvery { repository.getLobbyInfo(any()) } returns
LobbyResponse(
lobbyCode = "1234",
@@ -366,12 +370,11 @@ class LobbyViewModelTest {
false,
)
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.startPollingForTest("1234")
- advanceTimeBy(2000)
- advanceTimeBy(2000)
+ advanceTimeBy(4000.milliseconds)
viewModel.stopPollingForTest()
@@ -383,20 +386,17 @@ class LobbyViewModelTest {
@Test
fun testPolling_stateIsUpdated() =
runTest {
- val repository = mockk()
-
coEvery { repository.getLobbyInfo(any()) } returnsMany
listOf(
LobbyResponse("1", emptyList(), false),
LobbyResponse("2", emptyList(), false),
)
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.startPollingForTest("1234")
- advanceTimeBy(2000)
- advanceTimeBy(2000)
+ advanceTimeBy(4000.milliseconds)
viewModel.stopPollingForTest()
@@ -406,8 +406,6 @@ class LobbyViewModelTest {
@Test
fun testPolling_doesNotStartTwice() =
runTest {
- val repository = mockk()
-
coEvery { repository.getLobbyInfo("1234") } returns
LobbyResponse(
lobbyCode = "1234",
@@ -415,16 +413,15 @@ class LobbyViewModelTest {
false,
)
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.startPollingForTest("1234")
- advanceTimeBy(2000)
- advanceTimeBy(2000)
+ advanceTimeBy(4000.milliseconds)
viewModel.startPollingForTest("2345")
- advanceTimeBy(2000)
+ advanceTimeBy(2000.milliseconds)
viewModel.stopPollingForTest()
@@ -437,8 +434,6 @@ class LobbyViewModelTest {
@Test
fun testCreateLobby_alreadyInLobby() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "1234",
@@ -447,8 +442,9 @@ class LobbyViewModelTest {
)
coEvery { repository.createLobby("User") } returns response
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.createLobby("User")
@@ -468,8 +464,6 @@ class LobbyViewModelTest {
@Test
fun testJoinLobby_alreadyInLobby() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "1234",
@@ -478,8 +472,9 @@ class LobbyViewModelTest {
)
coEvery { repository.createLobby("User") } returns response
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.createLobby("User")
@@ -499,11 +494,9 @@ class LobbyViewModelTest {
@Test
fun testLeaveLobby_notInLobby() =
runTest {
- val repository = mockk()
+ val viewModel = LobbyViewModel(repository, sessionRepository)
- val viewModel = LobbyViewModel(repository)
-
- viewModel.leaveLobby("User", onResult = {})
+ viewModel.leaveLobby(UUID.randomUUID(), onResult = {})
advanceUntilIdle()
@@ -515,13 +508,40 @@ class LobbyViewModelTest {
}
@Test
- fun testChangeRoles_notInLobby() =
+ fun testLeaveLobby_nullIdDoesNothing() =
runTest {
- val repository = mockk()
+ val viewModel = LobbyViewModel(repository, sessionRepository)
+ val username = "User"
+ val lobbyCode = "ABCDE"
+ val onResult = { bool: Boolean -> }
+
+ val response =
+ LobbyResponse(
+ lobbyCode = "ABCDE",
+ playerList = listOf(PlayerDto("User", null, Team.RED, true)),
+ false,
+ )
- val viewModel = LobbyViewModel(repository)
+ coEvery {
+ repository.joinLobby(username, lobbyCode)
+ } returns response
+ coEvery { sessionRepository.clearUserId() } just Runs
- viewModel.changeRole(Role.OPERATIVE, Team.RED, "User")
+ viewModel.joinLobby(username, lobbyCode)
+
+ advanceUntilIdle()
+
+ viewModel.leaveLobby(null, onResult)
+
+ assertNotNull(viewModel.state.value.error)
+ }
+
+ @Test
+ fun testChangeRoles_notInLobby() =
+ runTest {
+ val viewModel = LobbyViewModel(repository, sessionRepository)
+
+ viewModel.changeRole(Role.OPERATIVE, Team.RED, "User", UUID.randomUUID())
advanceUntilIdle()
@@ -534,32 +554,39 @@ class LobbyViewModelTest {
@Test
fun changeRole_DelegatesCorrectly1() {
- val repository = mockk()
- val viewModel = spyk(LobbyViewModel(repository), recordPrivateCalls = true)
- viewModel.changeRole(PlayerRoles.BLUE_SPYMASTER, "Alice")
+ val viewModel = spyk(LobbyViewModel(repository, sessionRepository), recordPrivateCalls = true)
+ viewModel.changeRole(PlayerRoles.BLUE_SPYMASTER, "Alice", UUID.randomUUID())
verify {
- viewModel.changeRole(Role.SPYMASTER, Team.BLUE, "Alice")
+ viewModel.changeRole(Role.SPYMASTER, Team.BLUE, "Alice", any())
}
}
@Test
fun changeRole_DelegatesCorrectly2() {
- val repository = mockk()
- val viewModel = spyk(LobbyViewModel(repository), recordPrivateCalls = true)
+ val viewModel = spyk(LobbyViewModel(repository, sessionRepository), recordPrivateCalls = true)
- viewModel.changeRole(PlayerRoles.RED_OPERATIVE, "Bob")
+ viewModel.changeRole(PlayerRoles.RED_OPERATIVE, "Bob", UUID.randomUUID())
verify {
- viewModel.changeRole(Role.OPERATIVE, Team.RED, "Bob")
+ viewModel.changeRole(Role.OPERATIVE, Team.RED, "Bob", any())
}
}
+ @Test
+ fun changeRole_DoesNothingWithoutId() =
+ runTest {
+ val viewModel = LobbyViewModel(repository, sessionRepository)
+
+ viewModel.changeRole(PlayerRoles.RED_SPYMASTER, "User", null)
+
+ assertNotNull(viewModel.state.value.error)
+ }
+
@Test
fun `getRoleForUser returns BLUE_OPERATIVE`() =
runTest {
- val repository = mockk()
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
val players =
listOf(
@@ -582,6 +609,8 @@ class LobbyViewModelTest {
repository.joinLobby("Max", "ABCD")
} returns response
+ coEvery { sessionRepository.clearUserId() } just Runs
+
viewModel.joinLobby("Max", "ABCD")
advanceUntilIdle()
@@ -594,8 +623,7 @@ class LobbyViewModelTest {
@Test
fun `getRoleForUser returns NONE when player does not exist`() =
runTest {
- val repository = mockk()
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
val result = viewModel.getRoleForUser("Unknown")
@@ -605,8 +633,7 @@ class LobbyViewModelTest {
@Test
fun `changeRole updates player role correctly`() =
runTest {
- val repository = mockk()
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
val initialPlayers =
listOf(
@@ -642,13 +669,17 @@ class LobbyViewModelTest {
false,
)
+ val userId = UUID.randomUUID()
+
coEvery {
repository.joinLobby("Max", "ABCD")
} returns joinResponse
+ coEvery { sessionRepository.clearUserId() } just Runs
coEvery {
repository.changeRole(
"Max",
+ userId,
"ABCD",
Role.SPYMASTER,
Team.RED,
@@ -662,6 +693,7 @@ class LobbyViewModelTest {
role = Role.SPYMASTER,
team = Team.RED,
username = "Max",
+ userId = userId,
)
advanceUntilIdle()
@@ -673,6 +705,7 @@ class LobbyViewModelTest {
coVerify(exactly = 1) {
repository.changeRole(
"Max",
+ any(),
"ABCD",
Role.SPYMASTER,
Team.RED,
@@ -683,13 +716,13 @@ class LobbyViewModelTest {
@Test
fun `changeRole sets error when not in lobby`() =
runTest {
- val repository = mockk()
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.changeRole(
role = Role.SPYMASTER,
team = Team.RED,
username = "Max",
+ userId = UUID.randomUUID(),
)
advanceUntilIdle()
@@ -702,36 +735,32 @@ class LobbyViewModelTest {
@Test
fun changeRole_DelegatesCorrectly_redSpymaster() {
- val repository = mockk()
- val viewModel = spyk(LobbyViewModel(repository))
+ val viewModel = spyk(LobbyViewModel(repository, sessionRepository))
- viewModel.changeRole(PlayerRoles.RED_SPYMASTER, "Alice")
+ viewModel.changeRole(PlayerRoles.RED_SPYMASTER, "Alice", UUID.randomUUID())
verify {
- viewModel.changeRole(Role.SPYMASTER, Team.RED, "Alice")
+ viewModel.changeRole(Role.SPYMASTER, Team.RED, "Alice", any())
}
}
@Test
fun changeRole_DelegatesCorrectly_blueOperative() {
- val repository = mockk()
- val viewModel = spyk(LobbyViewModel(repository))
+ val viewModel = spyk(LobbyViewModel(repository, sessionRepository))
- viewModel.changeRole(PlayerRoles.BLUE_OPERATIVE, "Bob")
+ viewModel.changeRole(PlayerRoles.BLUE_OPERATIVE, "Bob", UUID.randomUUID())
verify {
- viewModel.changeRole(Role.OPERATIVE, Team.BLUE, "Bob")
+ viewModel.changeRole(Role.OPERATIVE, Team.BLUE, "Bob", any())
}
}
@Test
fun changeRole_invalidRole_setsError() =
runTest {
- val repository = mockk()
+ val viewModel = LobbyViewModel(repository, sessionRepository)
- val viewModel = LobbyViewModel(repository)
-
- viewModel.changeRole(PlayerRoles.NONE, "User")
+ viewModel.changeRole(PlayerRoles.NONE, "User", UUID.randomUUID())
advanceUntilIdle()
@@ -743,8 +772,7 @@ class LobbyViewModelTest {
@Test
fun getRoleForUser_userNotFound_returnsNone() {
- val repository = mockk()
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
val result = viewModel.getRoleForUser("Unknown")
@@ -754,11 +782,9 @@ class LobbyViewModelTest {
@Test
fun sendStartGame_blankUsername_doesNothing() =
runTest {
- val repository = mockk(relaxed = true)
-
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
- viewModel.sendStartGame("")
+ viewModel.sendStartGame("", UUID.randomUUID())
advanceUntilIdle()
@@ -768,10 +794,20 @@ class LobbyViewModelTest {
}
@Test
- fun getRoleForUser_blueOperative_returnsCorrectRole() =
+ fun sendStartGame_NullId_doesNothing() =
runTest {
- val repository = mockk()
+ val viewModel = LobbyViewModel(repository, sessionRepository)
+ viewModel.sendStartGame("User", null)
+ advanceUntilIdle()
+ coVerify(exactly = 0) {
+ repository.sendStartGame("User", any())
+ }
+ }
+
+ @Test
+ fun getRoleForUser_blueOperative_returnsCorrectRole() =
+ runTest {
val response =
LobbyResponse(
lobbyCode = "12345",
@@ -791,7 +827,9 @@ class LobbyViewModelTest {
repository.joinLobby("Alice", "12345")
} returns response
- val viewModel = LobbyViewModel(repository)
+ coEvery { sessionRepository.clearUserId() } just Runs
+
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Alice", "12345")
@@ -805,8 +843,6 @@ class LobbyViewModelTest {
@Test
fun getRoleForUser_redSpymaster_returnsCorrectRole() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "12345",
@@ -826,7 +862,9 @@ class LobbyViewModelTest {
repository.joinLobby("Bob", "12345")
} returns response
- val viewModel = LobbyViewModel(repository)
+ coEvery { sessionRepository.clearUserId() } just Runs
+
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Bob", "12345")
@@ -840,8 +878,6 @@ class LobbyViewModelTest {
@Test
fun getRoleForUser_nullRole_returnsNone() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "12345",
@@ -861,7 +897,11 @@ class LobbyViewModelTest {
repository.joinLobby("Bob", "12345")
} returns response
- val viewModel = LobbyViewModel(repository)
+ coEvery {
+ sessionRepository.clearUserId()
+ } just Runs
+
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Bob", "12345")
@@ -875,8 +915,6 @@ class LobbyViewModelTest {
@Test
fun getIsHost_returnsTrueForHost() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "12345",
@@ -896,7 +934,9 @@ class LobbyViewModelTest {
repository.joinLobby("Host", "12345")
} returns response
- val viewModel = LobbyViewModel(repository)
+ coEvery { sessionRepository.clearUserId() } just Runs
+
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Host", "12345")
@@ -908,8 +948,6 @@ class LobbyViewModelTest {
@Test
fun getIsHost_returnsFalseForNonHost() =
runTest {
- val repository = mockk()
-
val response =
LobbyResponse(
lobbyCode = "12345",
@@ -927,7 +965,11 @@ class LobbyViewModelTest {
repository.joinLobby("User", "12345")
} returns response
- val viewModel = LobbyViewModel(repository)
+ coEvery {
+ sessionRepository.clearUserId()
+ } just Runs
+
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("User", "12345")
@@ -942,8 +984,6 @@ class LobbyViewModelTest {
mockkStatic(Log::class)
every { Log.d(any(), any()) } returns 0
- val repository = mockk()
-
val joinResponse =
LobbyResponse(
lobbyCode = "12345",
@@ -969,21 +1009,23 @@ class LobbyViewModelTest {
} returns joinResponse
coEvery {
- repository.sendStartGame("12345", "Host")
+ repository.sendStartGame("12345", any())
} returns startResponse
- val viewModel = LobbyViewModel(repository)
+ coEvery { sessionRepository.clearUserId() } just Runs
+
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Host", "12345")
advanceUntilIdle()
- viewModel.sendStartGame("Host")
+ viewModel.sendStartGame("Host", UUID.randomUUID())
advanceUntilIdle()
coVerify {
- repository.sendStartGame("12345", "Host")
+ repository.sendStartGame("12345", any())
}
}
@@ -993,8 +1035,6 @@ class LobbyViewModelTest {
mockkStatic(Log::class)
every { Log.d(any(), any()) } returns 0
- val repository = mockk()
-
val joinResponse =
LobbyResponse(
lobbyCode = "12345",
@@ -1016,13 +1056,15 @@ class LobbyViewModelTest {
repository.sendStartGame(any(), any())
} throws RuntimeException("Start failed")
- val viewModel = LobbyViewModel(repository)
+ coEvery { sessionRepository.clearUserId() } just Runs
+
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Host", "12345")
advanceUntilIdle()
- viewModel.sendStartGame("Host")
+ viewModel.sendStartGame("Host", UUID.randomUUID())
advanceUntilIdle()
@@ -1034,8 +1076,7 @@ class LobbyViewModelTest {
@Test
fun getAvailableChatTabsForUser_unknownUser_returnsGlobalOnly() {
- val repository = mockk()
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
val result = viewModel.getAvailableChatTabsForUser("Unknown")
@@ -1045,7 +1086,6 @@ class LobbyViewModelTest {
@Test
fun getAvailableChatTabsForUser_playerWithoutTeam_returnsGlobalOnly() =
runTest {
- val repository = mockk()
val response =
LobbyResponse(
lobbyCode = "12345",
@@ -1055,11 +1095,12 @@ class LobbyViewModelTest {
coEvery { repository.joinLobby("Alice", "12345") } returns response
coEvery { repository.getLobbyInfo("12345") } returns response
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Alice", "12345")
- advanceTimeBy(1)
+ advanceTimeBy(1.milliseconds)
viewModel.stopPollingForTest()
assertEquals(listOf(ChatTab.GLOBAL), viewModel.getAvailableChatTabsForUser("Alice"))
@@ -1068,7 +1109,6 @@ class LobbyViewModelTest {
@Test
fun getAvailableChatTabsForUser_spymasterWithTeam_returnsGlobalAndTeam() =
runTest {
- val repository = mockk()
val response =
LobbyResponse(
lobbyCode = "12345",
@@ -1078,11 +1118,12 @@ class LobbyViewModelTest {
coEvery { repository.joinLobby("Alice", "12345") } returns response
coEvery { repository.getLobbyInfo("12345") } returns response
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Alice", "12345")
- advanceTimeBy(1)
+ advanceTimeBy(1.milliseconds)
viewModel.stopPollingForTest()
assertEquals(listOf(ChatTab.GLOBAL, ChatTab.TEAM), viewModel.getAvailableChatTabsForUser("Alice"))
@@ -1091,7 +1132,6 @@ class LobbyViewModelTest {
@Test
fun getAvailableChatTabsForUser_singleOperative_returnsGlobalAndTeam() =
runTest {
- val repository = mockk()
val response =
LobbyResponse(
lobbyCode = "12345",
@@ -1105,11 +1145,12 @@ class LobbyViewModelTest {
coEvery { repository.joinLobby("Alice", "12345") } returns response
coEvery { repository.getLobbyInfo("12345") } returns response
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Alice", "12345")
- advanceTimeBy(1)
+ advanceTimeBy(1.milliseconds)
viewModel.stopPollingForTest()
assertEquals(
@@ -1121,7 +1162,6 @@ class LobbyViewModelTest {
@Test
fun getAvailableChatTabsForUser_multipleSameTeamOperatives_returnsAllTabs() =
runTest {
- val repository = mockk()
val response =
LobbyResponse(
lobbyCode = "12345",
@@ -1135,11 +1175,12 @@ class LobbyViewModelTest {
coEvery { repository.joinLobby("Alice", "12345") } returns response
coEvery { repository.getLobbyInfo("12345") } returns response
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.joinLobby("Alice", "12345")
- advanceTimeBy(1)
+ advanceTimeBy(1.milliseconds)
viewModel.stopPollingForTest()
assertEquals(
@@ -1151,11 +1192,10 @@ class LobbyViewModelTest {
@Test
fun clearError_resetsError() =
runTest {
- val repository = mockk()
-
coEvery { repository.createLobby(any()) } throws RuntimeException("Network error")
+ coEvery { sessionRepository.clearUserId() } just Runs
- val viewModel = LobbyViewModel(repository)
+ val viewModel = LobbyViewModel(repository, sessionRepository)
viewModel.createLobby("Max")
diff --git a/app/src/test/java/com/codenames/frontend/viewmodel/SessionViewModelTest.kt b/app/src/test/java/com/codenames/frontend/viewmodel/SessionViewModelTest.kt
index 43b90c7..f6e6c06 100644
--- a/app/src/test/java/com/codenames/frontend/viewmodel/SessionViewModelTest.kt
+++ b/app/src/test/java/com/codenames/frontend/viewmodel/SessionViewModelTest.kt
@@ -75,28 +75,20 @@ class SessionViewModelTest {
}
@Test
- fun `setUserId updates user id`() {
- val uuid = UUID.randomUUID()
-
- viewModel.setUserId(uuid)
-
- assertEquals(uuid, viewModel.userState.value.userId)
- }
-
- @Test
- fun `persistUserState saves user`() =
+ fun `persistUserState saves username`() =
runTest {
val uuid = UUID.randomUUID()
+ viewModel.persistUserId(uuid)
+
viewModel.setUsername("Max")
- viewModel.setUserId(uuid)
viewModel.persistUserState()
advanceUntilIdle()
coVerify {
- sessionRepository.saveUser("Max", uuid)
+ sessionRepository.saveUser("Max", any())
}
}