From 046d9c6746e176ea2f9acc01db12e4e77000f58a Mon Sep 17 00:00:00 2001 From: Tom Calver Date: Sun, 28 Jun 2026 19:22:37 +0100 Subject: [PATCH 1/4] #186: Use correct supabase vars --- .github/workflows/release-artefact.yml | 4 ++-- gradle.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-artefact.yml b/.github/workflows/release-artefact.yml index ccd5d9d..faf089b 100644 --- a/.github/workflows/release-artefact.yml +++ b/.github/workflows/release-artefact.yml @@ -29,8 +29,8 @@ jobs: - name: Build Distribution Zip env: USE_LOCAL_MOCKS: "false" - SERVER_URL: ${{ secrets.SUPABASE_SERVER_URL }} - CLIENT_KEY: ${{ secrets.SUPABASE_CLIENT_KEY }} + SERVER_URL: ${{ secrets.SERVER_URL }} + CLIENT_KEY: ${{ secrets.CLIENT_KEY }} run: ./gradlew distZip --no-daemon - name: Extract Version diff --git a/gradle.properties b/gradle.properties index eaa7d85..72acb9c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ kotlin.code.style=official # Determines whether this project should use hardcoded mocks or Supabase-CLI. # See https://github.com/PPartisan/Roky/wiki/Project-Setup USE_LOCAL_MOCKS=true -version=0.1.0 +version=0.1.1 From f52fbb323e6f3a2edc40e74e72919dc35d2ac89f Mon Sep 17 00:00:00 2001 From: KaiRaiChu Date: Thu, 11 Jun 2026 18:57:04 +0100 Subject: [PATCH 2/4] #181: create displayble messages class --- .../viewmessages/DisplayableMessages.kt | 22 ++++++++++++ .../chatroom/viewmessages/ViewMessages.kt | 3 +- .../viewmessages/ViewMessagesPresenter.kt | 17 +++------ .../viewmessages/DisplayableMessagesTest.kt | 28 +++++++++++++++ .../viewmessages/ViewMessagesPresenterTest.kt | 36 ++++--------------- 5 files changed, 63 insertions(+), 43 deletions(-) create mode 100644 src/main/kotlin/chatroom/viewmessages/DisplayableMessages.kt create mode 100644 src/test/kotlin/chatroom/viewmessages/DisplayableMessagesTest.kt diff --git a/src/main/kotlin/chatroom/viewmessages/DisplayableMessages.kt b/src/main/kotlin/chatroom/viewmessages/DisplayableMessages.kt new file mode 100644 index 0000000..4d84417 --- /dev/null +++ b/src/main/kotlin/chatroom/viewmessages/DisplayableMessages.kt @@ -0,0 +1,22 @@ +package chatroom.viewmessages + +import chatserver.ChatRepositories +import kotlinx.coroutines.flow.* + +class DisplayableMessages(private val repositories: ChatRepositories) { + operator fun invoke(): Flow { + val messages = + repositories.readMessages().observe() + .filter { it.isOk } + .map { it.item.lastOrNull() } + .filterNotNull() + val username = + repositories.readProfiles().observe() + .filter { it.isOk } + .map { it.item } + return combine(messages, username) { message, usernames -> + val user = usernames[message.userId]?.username ?: "anon" + "$user: ${message.message}" + } + } +} diff --git a/src/main/kotlin/chatroom/viewmessages/ViewMessages.kt b/src/main/kotlin/chatroom/viewmessages/ViewMessages.kt index e14bb92..7dc2d2a 100644 --- a/src/main/kotlin/chatroom/viewmessages/ViewMessages.kt +++ b/src/main/kotlin/chatroom/viewmessages/ViewMessages.kt @@ -12,9 +12,10 @@ val viewMessagesModule = ViewMessagesPresenter( windowScope = get().windowScope, dispatchers = get(), - read = get().readMessages(), + read = get(), channel = get().subscribeMessages(), ) } + scoped { DisplayableMessages(get()) } } } diff --git a/src/main/kotlin/chatroom/viewmessages/ViewMessagesPresenter.kt b/src/main/kotlin/chatroom/viewmessages/ViewMessagesPresenter.kt index ac3e8ca..2ccf2be 100644 --- a/src/main/kotlin/chatroom/viewmessages/ViewMessagesPresenter.kt +++ b/src/main/kotlin/chatroom/viewmessages/ViewMessagesPresenter.kt @@ -4,33 +4,26 @@ import arch.Presenter import arch.RokyDispatchers import chatroom.viewmessages.ViewMessagesViewState.Messages import chatroom.viewmessages.ViewMessagesViewState.NoMessages -import chatserver.MessageResult -import chatserver.ReadChatRepository import chatserver.SubscribeChatRepository import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class ViewMessagesPresenter( private val windowScope: CoroutineScope, - private val read: ReadChatRepository, + private val read: DisplayableMessages, private val channel: SubscribeChatRepository, dispatchers: RokyDispatchers, ) : Presenter(dispatchers) { override fun onAttach(view: ViewMessagesView) { view.show(NoMessages) windowScope.launch(dispatchers.io) { - read.observe() - .filter { it.isOk } - .map { it.item.lastOrNull()?.let { "${it.userId}: ${it.message}" }.orEmpty() } - .map(::Messages) - .collect { message -> - withContext(dispatchers.main) { - withView { it.show(message) } - } + read().map(::Messages).collect { message -> + withContext(dispatchers.main) { + withView { it.show(message) } } + } } channel.subscribe() } diff --git a/src/test/kotlin/chatroom/viewmessages/DisplayableMessagesTest.kt b/src/test/kotlin/chatroom/viewmessages/DisplayableMessagesTest.kt new file mode 100644 index 0000000..0392072 --- /dev/null +++ b/src/test/kotlin/chatroom/viewmessages/DisplayableMessagesTest.kt @@ -0,0 +1,28 @@ +package chatroom.viewmessages + +import chatserver.ChatRepositories +import chatserver.MessageResult +import chatserver.ProfileResult +import chatserver.ReadChatRepository +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import org.junit.jupiter.api.BeforeEach + +class DisplayableMessagesTest { + private lateinit var users: ReadChatRepository + private lateinit var messages: ReadChatRepository + private lateinit var displayableMessages: DisplayableMessages + + @BeforeEach + fun setUp() { + users = mockk>().also { every { it.observe() } returns flowOf() } + messages = mockk>().also { every { it.observe() } returns flowOf() } + val repository = + mockk().also { + every { it.readProfiles() } returns users + every { it.readMessages() } returns messages + } + displayableMessages = DisplayableMessages(repository) + } +} diff --git a/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt b/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt index 3d94640..3c5a830 100644 --- a/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt +++ b/src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt @@ -3,9 +3,6 @@ package chatroom.viewmessages import arch.RokyDispatchers import chatroom.viewmessages.ViewMessagesViewState.Messages import chatroom.viewmessages.ViewMessagesViewState.NoMessages -import chatserver.Message -import chatserver.MessageResult -import chatserver.ReadChatRepository import chatserver.SubscribeChatRepository import io.mockk.* import kotlinx.coroutines.CoroutineScope @@ -22,7 +19,7 @@ import kotlin.test.assertTrue @OptIn(ExperimentalCoroutinesApi::class) class ViewMessagesPresenterTest { private lateinit var channel: SubscribeChatRepository - private lateinit var read: ReadChatRepository + private lateinit var read: DisplayableMessages private lateinit var scope: CoroutineScope private lateinit var view: ViewMessagesView private lateinit var presenter: ViewMessagesPresenter @@ -31,7 +28,7 @@ class ViewMessagesPresenterTest { fun setUp() { channel = mockk(relaxed = true) read = mockk() - every { read.observe() } returns flowOf() + every { read() } returns flowOf() view = mockk(relaxed = true) view = mockk(relaxed = true) val dispatchers: RokyDispatchers = @@ -55,8 +52,7 @@ class ViewMessagesPresenterTest { @Test fun `given message exist, when attached, then show message`() = runTest(dispatcher) { - val messages = listOf(Message("Kai", "Biggleswad ez badd")) - every { read.observe() } returns flowOf(MessageResult.ok(messages)) + every { read() } returns flowOf("Kai: Biggleswad ez badd") presenter.attach(view) advanceUntilIdle() verifyOrder { @@ -65,24 +61,6 @@ class ViewMessagesPresenterTest { } } - @Test - fun `given message exist, and message is not ok, when attached, then show nothing`() = - runTest(dispatcher) { - every { read.observe() } returns flowOf(MessageResult.fail(RuntimeException("Connection with server failed"))) - presenter.attach(view) - advanceUntilIdle() - verifyOrder { - view.show(NoMessages) - } - verify(exactly = 0) { - view.show( - withArg { - assertTrue { it is Messages } - }, - ) - } - } - @Test fun `when attached, then subscribe to chat messages`() = runTest(dispatcher) { @@ -101,12 +79,10 @@ class ViewMessagesPresenterTest { @Test fun `given two messages exist, when attached, then show two messages`() = runTest(dispatcher) { - val messageOne = listOf(Message("Kai", "Pokemon good")) - val messageTwo = listOf(Message("Allie", "Barry is the best")) - every { read.observe() } returns + every { read() } returns flowOf( - MessageResult.ok(messageOne), - MessageResult.ok(messageOne + messageTwo), + "Kai: Pokemon good", + "Allie: Barry is the best", ) presenter.attach(view) From ed7ac896ada6a3c0d052da532385644e74d37a7c Mon Sep 17 00:00:00 2001 From: KaiRaiChu Date: Tue, 30 Jun 2026 17:56:04 +0100 Subject: [PATCH 3/4] #181: add tests for displayble messages --- .../viewmessages/DisplayableMessages.kt | 25 +++---- .../viewmessages/DisplayableMessagesTest.kt | 69 +++++++++++++++++-- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/chatroom/viewmessages/DisplayableMessages.kt b/src/main/kotlin/chatroom/viewmessages/DisplayableMessages.kt index 4d84417..b0cd0cf 100644 --- a/src/main/kotlin/chatroom/viewmessages/DisplayableMessages.kt +++ b/src/main/kotlin/chatroom/viewmessages/DisplayableMessages.kt @@ -4,19 +4,14 @@ import chatserver.ChatRepositories import kotlinx.coroutines.flow.* class DisplayableMessages(private val repositories: ChatRepositories) { - operator fun invoke(): Flow { - val messages = - repositories.readMessages().observe() - .filter { it.isOk } - .map { it.item.lastOrNull() } - .filterNotNull() - val username = - repositories.readProfiles().observe() - .filter { it.isOk } - .map { it.item } - return combine(messages, username) { message, usernames -> - val user = usernames[message.userId]?.username ?: "anon" - "$user: ${message.message}" - } - } + operator fun invoke(): Flow = + repositories.readMessages().observe() + .filter { it.isOk } + .map { it.item.lastOrNull() } + .filterNotNull() + .map { message -> + val usernames = repositories.readProfiles().latest().takeIf { it.isOk }?.item ?: emptyMap() + val user = usernames[message.userId]?.username ?: "anon" + "$user: ${message.message}" + } } diff --git a/src/test/kotlin/chatroom/viewmessages/DisplayableMessagesTest.kt b/src/test/kotlin/chatroom/viewmessages/DisplayableMessagesTest.kt index 0392072..b972cac 100644 --- a/src/test/kotlin/chatroom/viewmessages/DisplayableMessagesTest.kt +++ b/src/test/kotlin/chatroom/viewmessages/DisplayableMessagesTest.kt @@ -1,14 +1,20 @@ package chatroom.viewmessages -import chatserver.ChatRepositories -import chatserver.MessageResult -import chatserver.ProfileResult -import chatserver.ReadChatRepository +import chatserver.* +import chatserver.profiles.SupabaseProfilesRepository.Profile +import io.kotest.matchers.collections.shouldContainExactly import io.mockk.every import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.time.Duration.Companion.seconds +@OptIn(ExperimentalCoroutinesApi::class) class DisplayableMessagesTest { private lateinit var users: ReadChatRepository private lateinit var messages: ReadChatRepository @@ -16,7 +22,13 @@ class DisplayableMessagesTest { @BeforeEach fun setUp() { - users = mockk>().also { every { it.observe() } returns flowOf() } + users = + mockk>().also { + every { it.latest() } returns + ProfileResult.ok( + emptyMap(), + ) + } messages = mockk>().also { every { it.observe() } returns flowOf() } val repository = mockk().also { @@ -25,4 +37,51 @@ class DisplayableMessagesTest { } displayableMessages = DisplayableMessages(repository) } + + @Test + fun `when known user sends message, then show username and message`() = + runTest { + every { users.latest() } returns ProfileResult.ok(mapOf("id" to Profile("id", "Tony KnowsItAll"))) + every { messages.observe() } returns flowOf(MessageResult.ok(listOf(Message("id", "I will fail everyone")))) + val emissions = mutableListOf() + backgroundScope.launch { displayableMessages().collect(emissions::add) } + advanceTimeBy(10.seconds) + emissions.shouldContainExactly("Tony KnowsItAll: I will fail everyone") + } + + @Test + fun `when unknown user post message, then show anon message`() = + runTest { + every { users.latest() } returns ProfileResult.ok(emptyMap()) + every { messages.observe() } returns flowOf(MessageResult.ok(listOf(Message("id", "I will fail everyone")))) + val emissions = mutableListOf() + backgroundScope.launch { displayableMessages().collect(emissions::add) } + advanceTimeBy(10.seconds) + emissions.shouldContainExactly("anon: I will fail everyone") + } + + @Test + fun `when know user sends message, and changes its name, then show new name`() = + runTest { + every { users.latest() } returns ProfileResult.ok(mapOf("id" to Profile("id", "Tony KnowsItAll"))) + every { messages.observe() } returns + flowOf( + MessageResult.ok(listOf(Message("id", "I will fail everyone"))), + ) + val emissions = mutableListOf() + backgroundScope.launch { displayableMessages().collect(emissions::add) } + advanceTimeBy(10.seconds) + + every { users.latest() } returns ProfileResult.ok(mapOf("id" to Profile("id", "Neamah"))) + every { messages.observe() } returns + flowOf( + MessageResult.ok(listOf(Message("id", "I hate my student, but not as much as i hate myself"))), + ) + backgroundScope.launch { displayableMessages().collect(emissions::add) } + advanceTimeBy(10.seconds) + emissions.shouldContainExactly( + "Tony KnowsItAll: I will fail everyone", + "Neamah: I hate my student, but not as much as i hate myself", + ) + } } From 10c25442a4c99e5d40e192b1bb7b570b6233861f Mon Sep 17 00:00:00 2001 From: Tom Calver Date: Tue, 30 Jun 2026 18:03:32 +0100 Subject: [PATCH 4/4] Upversion from 0.1.1 to 0.1.2 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 72acb9c..b528f1b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ kotlin.code.style=official # Determines whether this project should use hardcoded mocks or Supabase-CLI. # See https://github.com/PPartisan/Roky/wiki/Project-Setup USE_LOCAL_MOCKS=true -version=0.1.1 +version=0.1.2