auto-lock on background + reasoning/sources tests
Clear locked chat messages when app goes to background (onPauseOrDispose). Track viewingLockedChat state — reset on new chat or opening a non-locked conversation. Add tests: reasoning round-trip, null reasoning preserved, sources round-trip, empty sources → null, reasoning+sources together, SearchSource converter (empty/single/multi/malformed/ unknown fields).
This commit is contained in:
parent
edbdbd8697
commit
a16dfffd5c
3 changed files with 125 additions and 1 deletions
|
|
@ -118,6 +118,7 @@ fun ChatScreen(
|
|||
|
||||
// Conversation persistence: sidebar list from Room (instant), active conversation id
|
||||
var conversationId by remember { mutableStateOf<String?>(null) }
|
||||
var viewingLockedChat by remember { mutableStateOf(false) }
|
||||
val conversations by chat.conversationRepo.observeAll().collectAsState(initial = emptyList())
|
||||
|
||||
var loadError by remember { mutableStateOf<String?>(null) }
|
||||
|
|
@ -195,7 +196,12 @@ fun ChatScreen(
|
|||
session.config?.let { cfg ->
|
||||
scope.launch { KaizenApi.prewarm(cfg.baseUrl) }
|
||||
}
|
||||
onPauseOrDispose { }
|
||||
onPauseOrDispose {
|
||||
if (viewingLockedChat) {
|
||||
messages.clear()
|
||||
viewingLockedChat = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshConversations() {
|
||||
|
|
@ -207,6 +213,7 @@ fun ChatScreen(
|
|||
fun newChat() {
|
||||
messages.clear()
|
||||
conversationId = null
|
||||
viewingLockedChat = false
|
||||
}
|
||||
|
||||
fun openConversation(summary: ConversationSummary) {
|
||||
|
|
@ -244,6 +251,7 @@ fun ChatScreen(
|
|||
)
|
||||
}
|
||||
conversationId = summary.id
|
||||
viewingLockedChat = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -255,6 +263,7 @@ fun ChatScreen(
|
|||
}
|
||||
return
|
||||
}
|
||||
viewingLockedChat = false
|
||||
scope.launch {
|
||||
messages.clear()
|
||||
val cachedList = chat.messageRepo.getCached(summary.id)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package dev.kaizen.app
|
|||
|
||||
import dev.kaizen.app.db.Converters
|
||||
import dev.kaizen.app.net.Attachment
|
||||
import dev.kaizen.app.net.SearchSource
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
|
@ -61,4 +62,56 @@ class ConvertersTest {
|
|||
assertEquals(1, result.size)
|
||||
assertEquals("https://x.com/f", result[0].url)
|
||||
}
|
||||
|
||||
// --- SearchSource converter tests ---
|
||||
|
||||
@Test
|
||||
fun emptySourcesRoundTrip() {
|
||||
val json = converters.sourcesToJson(emptyList())
|
||||
val result = converters.jsonToSources(json)
|
||||
assertTrue(result.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleSourceRoundTrip() {
|
||||
val original = listOf(
|
||||
SearchSource(title = "Wikipedia", url = "https://en.wikipedia.org/wiki/Test", snippet = "A test article")
|
||||
)
|
||||
val json = converters.sourcesToJson(original)
|
||||
val result = converters.jsonToSources(json)
|
||||
|
||||
assertEquals(1, result.size)
|
||||
assertEquals("Wikipedia", result[0].title)
|
||||
assertEquals("https://en.wikipedia.org/wiki/Test", result[0].url)
|
||||
assertEquals("A test article", result[0].snippet)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multipleSourcesRoundTrip() {
|
||||
val original = listOf(
|
||||
SearchSource(title = "Source 1", url = "https://a.com"),
|
||||
SearchSource(title = "Source 2", url = "https://b.com", snippet = "Details"),
|
||||
)
|
||||
val json = converters.sourcesToJson(original)
|
||||
val result = converters.jsonToSources(json)
|
||||
|
||||
assertEquals(2, result.size)
|
||||
assertEquals("Source 1", result[0].title)
|
||||
assertEquals("Source 2", result[1].title)
|
||||
assertEquals("Details", result[1].snippet)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun malformedSourcesJsonReturnsEmptyList() {
|
||||
val result = converters.jsonToSources("{broken")
|
||||
assertTrue(result.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sourcesWithUnknownFieldsIgnored() {
|
||||
val json = """[{"title":"T","url":"https://x.com","snippet":"","futureField":"v"}]"""
|
||||
val result = converters.jsonToSources(json)
|
||||
assertEquals(1, result.size)
|
||||
assertEquals("T", result[0].title)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import dev.kaizen.app.db.toStoredMessage
|
|||
import dev.kaizen.app.db.toSummary
|
||||
import dev.kaizen.app.net.Attachment
|
||||
import dev.kaizen.app.net.ConversationSummary
|
||||
import dev.kaizen.app.net.SearchSource
|
||||
import dev.kaizen.app.net.StoredMessage
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -119,4 +121,64 @@ class MappersTest {
|
|||
assertEquals(original.attachments[0].mimeType, roundTripped.attachments[0].mimeType)
|
||||
assertEquals(original.attachments[0].size, roundTripped.attachments[0].size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reasoningPreservedInRoundTrip() {
|
||||
val original = StoredMessage(
|
||||
id = "msg-r", role = "assistant", content = "Answer",
|
||||
reasoning = "Let me think about this step by step...",
|
||||
)
|
||||
val roundTripped = original.toEntity("c1", 0).toStoredMessage()
|
||||
|
||||
assertEquals("Let me think about this step by step...", roundTripped.reasoning)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nullReasoningPreserved() {
|
||||
val original = StoredMessage(id = "msg-nr", role = "assistant", content = "Simple answer")
|
||||
val entity = original.toEntity("c1", 0)
|
||||
assertNull(entity.reasoning)
|
||||
val roundTripped = entity.toStoredMessage()
|
||||
assertNull(roundTripped.reasoning)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sourcesPreservedInRoundTrip() {
|
||||
val sources = listOf(
|
||||
SearchSource(title = "Wikipedia", url = "https://en.wikipedia.org/wiki/Test", snippet = "A test"),
|
||||
SearchSource(title = "MDN", url = "https://developer.mozilla.org", snippet = "Web docs"),
|
||||
)
|
||||
val original = StoredMessage(
|
||||
id = "msg-s", role = "assistant", content = "Search result",
|
||||
sources = sources,
|
||||
)
|
||||
val roundTripped = original.toEntity("c1", 0).toStoredMessage()
|
||||
|
||||
assertEquals(2, roundTripped.sources?.size)
|
||||
assertEquals("Wikipedia", roundTripped.sources!![0].title)
|
||||
assertEquals("https://developer.mozilla.org", roundTripped.sources!![1].url)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptySourcesBecomeNull() {
|
||||
val original = StoredMessage(id = "msg-es", role = "assistant", content = "No sources")
|
||||
val entity = original.toEntity("c1", 0)
|
||||
assertTrue(entity.sources.isEmpty())
|
||||
val roundTripped = entity.toStoredMessage()
|
||||
assertNull(roundTripped.sources)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reasoningAndSourcesTogether() {
|
||||
val original = StoredMessage(
|
||||
id = "msg-rs", role = "assistant", content = "Full answer",
|
||||
reasoning = "Thinking...",
|
||||
sources = listOf(SearchSource(title = "Src", url = "https://src.com")),
|
||||
)
|
||||
val roundTripped = original.toEntity("c1", 0).toStoredMessage()
|
||||
|
||||
assertEquals("Thinking...", roundTripped.reasoning)
|
||||
assertEquals(1, roundTripped.sources?.size)
|
||||
assertEquals("Src", roundTripped.sources!![0].title)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue