bugfixes
This commit is contained in:
+1
-1
@@ -200,7 +200,7 @@ class EventCollectionState<T : Any>(
|
|||||||
// Sort if comparator provided, otherwise keep newest first (pending items already at end)
|
// Sort if comparator provided, otherwise keep newest first (pending items already at end)
|
||||||
val sorted =
|
val sorted =
|
||||||
if (sortComparator != null) {
|
if (sortComparator != null) {
|
||||||
merged.sortedWith(sortComparator)
|
merged.sortedWith(sortComparator).distinctBy { getId(it) }
|
||||||
} else {
|
} else {
|
||||||
// Reverse so newest (pending) items come first
|
// Reverse so newest (pending) items come first
|
||||||
(pendingItems.reversed() + _items.value).distinctBy { getId(it) }
|
(pendingItems.reversed() + _items.value).distinctBy { getId(it) }
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.desktop
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug configuration for the desktop application.
|
||||||
|
*
|
||||||
|
* To enable debug mode, set the environment variable:
|
||||||
|
* AMETHYST_DEBUG=true
|
||||||
|
*
|
||||||
|
* Or run with:
|
||||||
|
* ./gradlew :desktopApp:run -PamethystDebug=true
|
||||||
|
*/
|
||||||
|
object DebugConfig {
|
||||||
|
/**
|
||||||
|
* Enables developer settings and debug features.
|
||||||
|
*
|
||||||
|
* When true, shows:
|
||||||
|
* - Raw nsec/npub in settings
|
||||||
|
* - Additional logging
|
||||||
|
* - Debug UI elements
|
||||||
|
*/
|
||||||
|
val isDebugMode: Boolean by lazy {
|
||||||
|
// Check environment variable
|
||||||
|
val envDebug = System.getenv("AMETHYST_DEBUG")?.toBoolean() ?: false
|
||||||
|
|
||||||
|
// Check system property (for gradle -PamethystDebug=true)
|
||||||
|
val propDebug = System.getProperty("amethyst.debug")?.toBoolean() ?: false
|
||||||
|
|
||||||
|
envDebug || propDebug
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log debug information.
|
||||||
|
*/
|
||||||
|
fun log(message: String) {
|
||||||
|
if (isDebugMode) {
|
||||||
|
println("[DEBUG] $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,21 +91,21 @@ private val isMacOS = System.getProperty("os.name").lowercase().contains("mac")
|
|||||||
* Desktop navigation state - extends AppScreen with dynamic destinations.
|
* Desktop navigation state - extends AppScreen with dynamic destinations.
|
||||||
*/
|
*/
|
||||||
sealed class DesktopScreen {
|
sealed class DesktopScreen {
|
||||||
data object Feed : DesktopScreen()
|
object Feed : DesktopScreen()
|
||||||
|
|
||||||
data object Search : DesktopScreen()
|
object Search : DesktopScreen()
|
||||||
|
|
||||||
data object Messages : DesktopScreen()
|
object Messages : DesktopScreen()
|
||||||
|
|
||||||
data object Notifications : DesktopScreen()
|
object Notifications : DesktopScreen()
|
||||||
|
|
||||||
data object MyProfile : DesktopScreen()
|
object MyProfile : DesktopScreen()
|
||||||
|
|
||||||
data class UserProfile(
|
data class UserProfile(
|
||||||
val pubKeyHex: String,
|
val pubKeyHex: String,
|
||||||
) : DesktopScreen()
|
) : DesktopScreen()
|
||||||
|
|
||||||
data object Settings : DesktopScreen()
|
object Settings : DesktopScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main() =
|
fun main() =
|
||||||
@@ -365,7 +365,7 @@ fun MainContent(
|
|||||||
onScreenChange(DesktopScreen.UserProfile(pubKeyHex))
|
onScreenChange(DesktopScreen.UserProfile(pubKeyHex))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
DesktopScreen.Settings -> RelaySettingsScreen(relayManager)
|
DesktopScreen.Settings -> RelaySettingsScreen(relayManager, account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,17 +405,37 @@ fun ProfileScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RelaySettingsScreen(relayManager: DesktopRelayConnectionManager) {
|
fun RelaySettingsScreen(
|
||||||
|
relayManager: DesktopRelayConnectionManager,
|
||||||
|
account: AccountState.LoggedIn,
|
||||||
|
) {
|
||||||
val relayStatuses by relayManager.relayStatuses.collectAsState()
|
val relayStatuses by relayManager.relayStatuses.collectAsState()
|
||||||
val connectedRelays by relayManager.connectedRelays.collectAsState()
|
val connectedRelays by relayManager.connectedRelays.collectAsState()
|
||||||
var newRelayUrl by remember { mutableStateOf("") }
|
var newRelayUrl by remember { mutableStateOf("") }
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
Text(
|
Text(
|
||||||
"Relay Settings",
|
"Settings",
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// Developer Settings Section (only in debug mode)
|
||||||
|
if (DebugConfig.isDebugMode) {
|
||||||
|
com.vitorpamplona.amethyst.desktop.ui
|
||||||
|
.DevSettingsSection(account = account)
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
|
HorizontalDivider()
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"Relay Settings",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -470,7 +490,7 @@ fun RelaySettingsScreen(relayManager: DesktopRelayConnectionManager) {
|
|||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
) {
|
) {
|
||||||
items(relayStatuses.values.toList()) { status ->
|
items(relayStatuses.values.toList(), key = { it.url.url }) { status ->
|
||||||
RelayStatusCard(
|
RelayStatusCard(
|
||||||
status = status,
|
status = status,
|
||||||
onRemove = { relayManager.removeRelay(status.url) },
|
onRemove = { relayManager.removeRelay(status.url) },
|
||||||
|
|||||||
+261
@@ -0,0 +1,261 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.desktop.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ContentCopy
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.commons.account.AccountState
|
||||||
|
import java.awt.Toolkit
|
||||||
|
import java.awt.datatransfer.StringSelection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Developer settings section - shows sensitive keys for debugging.
|
||||||
|
* Only enable in debug builds or with explicit debug flag.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun DevSettingsSection(
|
||||||
|
account: AccountState.LoggedIn,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
var showKeys by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.border(
|
||||||
|
width = 2.dp,
|
||||||
|
color = Color(0xFFFF9800), // Orange warning color
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
).background(
|
||||||
|
color = Color(0xFF332200), // Dark orange tint
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
).padding(16.dp),
|
||||||
|
) {
|
||||||
|
// Warning header
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Warning,
|
||||||
|
contentDescription = "Warning",
|
||||||
|
tint = Color(0xFFFF9800),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Developer Settings",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color(0xFFFF9800),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"Debug mode only - Handle keys with care",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
|
HorizontalDivider(color = Color(0xFFFF9800).copy(alpha = 0.3f))
|
||||||
|
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// Toggle button
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { showKeys = !showKeys },
|
||||||
|
colors =
|
||||||
|
ButtonDefaults.outlinedButtonColors(
|
||||||
|
contentColor = Color(0xFFFF9800),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(if (showKeys) "Hide Keys" else "Show Keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showKeys) {
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// npub
|
||||||
|
KeyRow(
|
||||||
|
label = "Public Key (npub)",
|
||||||
|
value = account.npub,
|
||||||
|
isSensitive = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
|
||||||
|
// nsec (only if available)
|
||||||
|
account.nsec?.let { nsec ->
|
||||||
|
KeyRow(
|
||||||
|
label = "Private Key (nsec)",
|
||||||
|
value = nsec,
|
||||||
|
isSensitive = true,
|
||||||
|
)
|
||||||
|
} ?: run {
|
||||||
|
Text(
|
||||||
|
"Read-only mode - No private key available",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
fontStyle = androidx.compose.ui.text.font.FontStyle.Italic,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
|
||||||
|
// Hex key
|
||||||
|
KeyRow(
|
||||||
|
label = "Public Key (hex)",
|
||||||
|
value = account.pubKeyHex,
|
||||||
|
isSensitive = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun KeyRow(
|
||||||
|
label: String,
|
||||||
|
value: String,
|
||||||
|
isSensitive: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
var copiedRecently by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Column(modifier = modifier.fillMaxWidth()) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color =
|
||||||
|
if (isSensitive) {
|
||||||
|
Color(0xFFFF5252)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurface
|
||||||
|
},
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
copyToClipboard(value)
|
||||||
|
copiedRecently = true
|
||||||
|
},
|
||||||
|
colors =
|
||||||
|
ButtonDefaults.buttonColors(
|
||||||
|
containerColor =
|
||||||
|
if (copiedRecently) {
|
||||||
|
Color(0xFF4CAF50)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
|
},
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.ContentCopy,
|
||||||
|
contentDescription = "Copy",
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
)
|
||||||
|
Text(if (copiedRecently) "Copied!" else "Copy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(4.dp))
|
||||||
|
|
||||||
|
// Display the key in monospace
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style =
|
||||||
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
),
|
||||||
|
color =
|
||||||
|
if (isSensitive) {
|
||||||
|
Color(0xFFFF5252)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
},
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = Color(0xFF1A1A1A),
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
).padding(8.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reset copied state after a delay
|
||||||
|
if (copiedRecently) {
|
||||||
|
androidx.compose.runtime.LaunchedEffect(Unit) {
|
||||||
|
kotlinx.coroutines.delay(2000)
|
||||||
|
copiedRecently = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy text to system clipboard using AWT Toolkit.
|
||||||
|
*/
|
||||||
|
private fun copyToClipboard(text: String) {
|
||||||
|
try {
|
||||||
|
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
|
||||||
|
val selection = StringSelection(text)
|
||||||
|
clipboard.setContents(selection, selection)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.desktop
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class DebugConfigTest {
|
||||||
|
@Test
|
||||||
|
fun testDebugModeCanBeQueried() {
|
||||||
|
// This test verifies that DebugConfig.isDebugMode is accessible
|
||||||
|
// The actual value depends on environment/system properties
|
||||||
|
val debugMode = DebugConfig.isDebugMode
|
||||||
|
// Just verify it returns a boolean (true or false)
|
||||||
|
assertTrue(debugMode || !debugMode, "isDebugMode should return a boolean value")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDebugLogDoesNotThrow() {
|
||||||
|
// Verify debug logging doesn't throw exceptions
|
||||||
|
try {
|
||||||
|
DebugConfig.log("Test debug message")
|
||||||
|
assertTrue(true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
assertFalse(true, "Debug log should not throw exceptions: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDebugConfigIsObject() {
|
||||||
|
// Verify DebugConfig is a singleton object
|
||||||
|
val instance1 = DebugConfig
|
||||||
|
val instance2 = DebugConfig
|
||||||
|
assertTrue(instance1 === instance2, "DebugConfig should be a singleton object")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user