feat(ui): add EditBotBottomSheet with avatar editor and bot deletion (#983)
This commit is contained in:
7 files changed
+411
No files matched your search
@@ -21,6 +21,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.filled.GroupAdd
|
import androidx.compose.material.icons.filled.GroupAdd
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material.icons.filled.Visibility
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
@@ -73,8 +74,26 @@ fun BotsScreen(
|
|||||||
var isSearchActive by remember { mutableStateOf(false) }
|
var isSearchActive by remember { mutableStateOf(false) }
|
||||||
var showCreateDialog by remember { mutableStateOf(false) }
|
var showCreateDialog by remember { mutableStateOf(false) }
|
||||||
var showCreateGroupDialog by remember { mutableStateOf(false) }
|
var showCreateGroupDialog by remember { mutableStateOf(false) }
|
||||||
|
var editingBot by remember { mutableStateOf<ProfileInfo?>(null) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
editingBot?.let { bot ->
|
||||||
|
EditBotBottomSheet(
|
||||||
|
bot = bot,
|
||||||
|
onDismiss = { editingBot = null },
|
||||||
|
onSave = { title, description, shape, color ->
|
||||||
|
viewModel.updateBotMeta(bot.name, title, description, shape, color) {
|
||||||
|
editingBot = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDelete = {
|
||||||
|
viewModel.deleteBot(bot.name) {
|
||||||
|
editingBot = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (showCreateDialog) {
|
if (showCreateDialog) {
|
||||||
CreateBotDialog(
|
CreateBotDialog(
|
||||||
onDismiss = { showCreateDialog = false },
|
onDismiss = { showCreateDialog = false },
|
||||||
@@ -264,6 +283,9 @@ fun BotsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onEditClick = {
|
||||||
|
editingBot = profile
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,6 +355,7 @@ private fun BotCard(
|
|||||||
profile: ProfileInfo,
|
profile: ProfileInfo,
|
||||||
isActiveProfile: Boolean,
|
isActiveProfile: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
onEditClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val botMeta = profile.botMeta()
|
val botMeta = profile.botMeta()
|
||||||
@@ -428,6 +451,16 @@ private fun BotCard(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = onEditClick,
|
||||||
|
modifier = Modifier.testTag("bot_edit_button_${profile.name}"),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Edit,
|
||||||
|
contentDescription = stringResource(R.string.bots_edit_title),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,6 +188,51 @@ class BotsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateBotMeta(
|
||||||
|
name: String,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
shape: String,
|
||||||
|
color: String,
|
||||||
|
onSuccess: () -> Unit,
|
||||||
|
) {
|
||||||
|
viewModelScope.launch(ioDispatcher) {
|
||||||
|
val bot = _uiState.value.profiles.find { it.name == name }
|
||||||
|
val existingMeta = bot?.botMeta() ?: BotRosterMeta()
|
||||||
|
val updatedMeta =
|
||||||
|
existingMeta.copy(
|
||||||
|
title = title.ifBlank { null },
|
||||||
|
description = description.ifBlank { null },
|
||||||
|
avatar =
|
||||||
|
BotAvatarMeta(
|
||||||
|
shape = shape,
|
||||||
|
color = color,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
wsClientConfigureBot(name, updatedMeta)
|
||||||
|
loadBots()
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteBot(
|
||||||
|
name: String,
|
||||||
|
onSuccess: () -> Unit,
|
||||||
|
) {
|
||||||
|
viewModelScope.launch(ioDispatcher) {
|
||||||
|
val result = safeApiCall { ApiClient.hermesApi.deleteProfile(name) }
|
||||||
|
if (result is NetworkResult.Success) {
|
||||||
|
loadBots()
|
||||||
|
onSuccess()
|
||||||
|
} else {
|
||||||
|
val err =
|
||||||
|
(result as? NetworkResult.Failure)?.error?.message
|
||||||
|
?: "Failed to delete bot"
|
||||||
|
_uiState.update { it.copy(errorMessage = err) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun createGroupChat(
|
fun createGroupChat(
|
||||||
groupName: String,
|
groupName: String,
|
||||||
botNames: List<String>,
|
botNames: List<String>,
|
||||||
|
|||||||
@@ -0,0 +1,277 @@
|
|||||||
|
package com.m57.hermescontrol.ui.bots
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.m57.hermescontrol.R
|
||||||
|
import com.m57.hermescontrol.data.model.BotAvatarMeta
|
||||||
|
import com.m57.hermescontrol.data.model.ProfileInfo
|
||||||
|
import com.m57.hermescontrol.theme.parseHexColor
|
||||||
|
import com.m57.hermescontrol.ui.common.BotAvatar
|
||||||
|
|
||||||
|
private val AVAILABLE_SHAPES = listOf("circle", "square", "rounded", "hexagon")
|
||||||
|
private val PALETTE_COLORS =
|
||||||
|
listOf(
|
||||||
|
"#4F46E5", // Indigo
|
||||||
|
"#2563EB", // Blue
|
||||||
|
"#0D9488", // Teal
|
||||||
|
"#16A34A", // Green
|
||||||
|
"#D97706", // Amber
|
||||||
|
"#EA580C", // Orange
|
||||||
|
"#DC2626", // Red
|
||||||
|
"#DB2777", // Pink
|
||||||
|
"#9333EA", // Purple
|
||||||
|
"#4B5563", // Gray
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun EditBotBottomSheet(
|
||||||
|
bot: ProfileInfo,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onSave: (title: String, description: String, shape: String, color: String) -> Unit,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
) {
|
||||||
|
val meta = bot.botMeta()
|
||||||
|
var title by remember { mutableStateOf(meta?.title ?: bot.effectiveTitle) }
|
||||||
|
var description by remember { mutableStateOf(meta?.description ?: bot.effectiveDescription) }
|
||||||
|
var selectedShape by remember { mutableStateOf(meta?.avatar?.shape ?: "circle") }
|
||||||
|
var selectedColor by remember { mutableStateOf(meta?.avatar?.color ?: PALETTE_COLORS[0]) }
|
||||||
|
var showDeleteConfirm by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showDeleteConfirm) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDeleteConfirm = false },
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.bots_edit_delete_confirm_title, bot.effectiveTitle),
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.bots_edit_delete_confirm_msg))
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
showDeleteConfirm = false
|
||||||
|
onDelete()
|
||||||
|
},
|
||||||
|
colors =
|
||||||
|
ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.error,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onError,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.bots_edit_delete))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showDeleteConfirm = false }) {
|
||||||
|
Text(stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 20.dp, vertical = 8.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.bots_edit_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Live Avatar Preview Header
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 12.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
BotAvatar(
|
||||||
|
name = bot.name,
|
||||||
|
avatar =
|
||||||
|
BotAvatarMeta(
|
||||||
|
shape = selectedShape,
|
||||||
|
color = selectedColor,
|
||||||
|
),
|
||||||
|
size = 64.dp,
|
||||||
|
showPresence = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = title,
|
||||||
|
onValueChange = { title = it },
|
||||||
|
label = { Text(stringResource(R.string.bots_create_title_label)) },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = description,
|
||||||
|
onValueChange = { description = it },
|
||||||
|
label = { Text(stringResource(R.string.bots_create_desc_label)) },
|
||||||
|
maxLines = 3,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.bots_create_avatar_shape),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
AVAILABLE_SHAPES.forEach { shape ->
|
||||||
|
FilterChip(
|
||||||
|
selected = selectedShape == shape,
|
||||||
|
onClick = { selectedShape = shape },
|
||||||
|
label = { Text(shape.replaceFirstChar { it.uppercase() }) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.bots_create_avatar_color),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
PALETTE_COLORS.take(5).forEach { hex ->
|
||||||
|
val color = parseHexColor(hex, Color.Gray)
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(color)
|
||||||
|
.clickable { selectedColor = hex }
|
||||||
|
.then(
|
||||||
|
if (selectedColor == hex) {
|
||||||
|
Modifier.border(2.dp, MaterialTheme.colorScheme.onSurface, CircleShape)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
PALETTE_COLORS.drop(5).forEach { hex ->
|
||||||
|
val color = parseHexColor(hex, Color.Gray)
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(color)
|
||||||
|
.clickable { selectedColor = hex }
|
||||||
|
.then(
|
||||||
|
if (selectedColor == hex) {
|
||||||
|
Modifier.border(2.dp, MaterialTheme.colorScheme.onSurface, CircleShape)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onSave(title.trim(), description.trim(), selectedShape, selectedColor)
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.bots_edit_save))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot.name != "default") {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { showDeleteConfirm = true },
|
||||||
|
colors =
|
||||||
|
ButtonDefaults.outlinedButtonColors(
|
||||||
|
contentColor = MaterialTheme.colorScheme.error,
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.bots_edit_delete))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -92,6 +92,11 @@
|
|||||||
<string name="bots_tab_groups">그룹</string>
|
<string name="bots_tab_groups">그룹</string>
|
||||||
<string name="bots_groups_empty_title">그룹 채팅 없음</string>
|
<string name="bots_groups_empty_title">그룹 채팅 없음</string>
|
||||||
<string name="bots_groups_empty_desc">봇 간 협업을 위한 다중 에이전트 그룹 채팅을 만드세요.</string>
|
<string name="bots_groups_empty_desc">봇 간 협업을 위한 다중 에이전트 그룹 채팅을 만드세요.</string>
|
||||||
|
<string name="bots_edit_title">봇 편집</string>
|
||||||
|
<string name="bots_edit_save">변경 사항 저장</string>
|
||||||
|
<string name="bots_edit_delete">봇 삭제</string>
|
||||||
|
<string name="bots_edit_delete_confirm_title">%1$s 삭제하시겠습니까?</string>
|
||||||
|
<string name="bots_edit_delete_confirm_msg">에이전트 프로필 및 해당 구성이 영구적으로 삭제됩니다.</string>
|
||||||
<string name="screen_webhooks">웹훅</string>
|
<string name="screen_webhooks">웹훅</string>
|
||||||
<string name="screen_gateway">게이트웨이</string>
|
<string name="screen_gateway">게이트웨이</string>
|
||||||
<string name="screen_toolsets">툴셋</string>
|
<string name="screen_toolsets">툴셋</string>
|
||||||
|
|||||||
@@ -96,6 +96,11 @@
|
|||||||
<string name="bots_tab_groups">群聊</string>
|
<string name="bots_tab_groups">群聊</string>
|
||||||
<string name="bots_groups_empty_title">暂无群聊</string>
|
<string name="bots_groups_empty_title">暂无群聊</string>
|
||||||
<string name="bots_groups_empty_desc">创建多智能体群聊以实现多 Bot 协同。</string>
|
<string name="bots_groups_empty_desc">创建多智能体群聊以实现多 Bot 协同。</string>
|
||||||
|
<string name="bots_edit_title">编辑 Bot</string>
|
||||||
|
<string name="bots_edit_save">保存更改</string>
|
||||||
|
<string name="bots_edit_delete">删除 Bot</string>
|
||||||
|
<string name="bots_edit_delete_confirm_title">删除 %1$s?</string>
|
||||||
|
<string name="bots_edit_delete_confirm_msg">这将永久删除该智能体配置文件及其配置。</string>
|
||||||
<string name="screen_webhooks">Webhook</string>
|
<string name="screen_webhooks">Webhook</string>
|
||||||
<string name="screen_gateway">网关</string>
|
<string name="screen_gateway">网关</string>
|
||||||
<string name="screen_toolsets">工具集</string>
|
<string name="screen_toolsets">工具集</string>
|
||||||
|
|||||||
@@ -109,6 +109,11 @@
|
|||||||
<string name="bots_tab_groups">Groups</string>
|
<string name="bots_tab_groups">Groups</string>
|
||||||
<string name="bots_groups_empty_title">No Group Chats</string>
|
<string name="bots_groups_empty_title">No Group Chats</string>
|
||||||
<string name="bots_groups_empty_desc">Create a multi-agent group chat to let bots collaborate.</string>
|
<string name="bots_groups_empty_desc">Create a multi-agent group chat to let bots collaborate.</string>
|
||||||
|
<string name="bots_edit_title">Edit Bot</string>
|
||||||
|
<string name="bots_edit_save">Save Changes</string>
|
||||||
|
<string name="bots_edit_delete">Delete Bot</string>
|
||||||
|
<string name="bots_edit_delete_confirm_title">Delete %1$s?</string>
|
||||||
|
<string name="bots_edit_delete_confirm_msg">This will permanently delete the agent profile and its configuration.</string>
|
||||||
<string name="screen_webhooks">Webhooks</string>
|
<string name="screen_webhooks">Webhooks</string>
|
||||||
<string name="screen_gateway">Gateway</string>
|
<string name="screen_gateway">Gateway</string>
|
||||||
<string name="screen_toolsets">Toolsets</string>
|
<string name="screen_toolsets">Toolsets</string>
|
||||||
|
|||||||
@@ -190,4 +190,45 @@ class BotsViewModelTest {
|
|||||||
|
|
||||||
assertTrue(success)
|
assertTrue(success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUpdateBotMeta_sendsRpcAndReloads() =
|
||||||
|
runTest {
|
||||||
|
val profiles = listOf(ProfileInfo(name = "scout"))
|
||||||
|
coEvery { mockApi.getProfiles() } returns Response.success(ProfilesResponse(profiles))
|
||||||
|
coEvery { mockApi.getActiveProfile() } returns Response.success(ActiveProfileResponse(active = "scout"))
|
||||||
|
|
||||||
|
val viewModel = BotsViewModel(ioDispatcher = testDispatcher, autoLoad = false)
|
||||||
|
viewModel.loadBots()
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
var success = false
|
||||||
|
viewModel.updateBotMeta(
|
||||||
|
name = "scout",
|
||||||
|
title = "Scout Lead",
|
||||||
|
description = "Updated desc",
|
||||||
|
shape = "square",
|
||||||
|
color = "#2563EB",
|
||||||
|
onSuccess = { success = true },
|
||||||
|
)
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assertTrue(success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteBot_callsDeleteApiAndReloads() =
|
||||||
|
runTest {
|
||||||
|
coEvery { mockApi.deleteProfile("scout") } returns Response.success(Unit)
|
||||||
|
coEvery { mockApi.getProfiles() } returns Response.success(ProfilesResponse(emptyList()))
|
||||||
|
coEvery { mockApi.getActiveProfile() } returns Response.success(ActiveProfileResponse(active = "default"))
|
||||||
|
|
||||||
|
val viewModel = BotsViewModel(ioDispatcher = testDispatcher, autoLoad = false)
|
||||||
|
var success = false
|
||||||
|
viewModel.deleteBot("scout") { success = true }
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assertTrue(success)
|
||||||
|
coVerify(exactly = 1) { mockApi.deleteProfile("scout") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user