Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release-artefact.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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.2
17 changes: 17 additions & 0 deletions src/main/kotlin/chatroom/viewmessages/DisplayableMessages.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package chatroom.viewmessages

import chatserver.ChatRepositories
import kotlinx.coroutines.flow.*

class DisplayableMessages(private val repositories: ChatRepositories) {
operator fun invoke(): Flow<String> =
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}"
}
}
3 changes: 2 additions & 1 deletion src/main/kotlin/chatroom/viewmessages/ViewMessages.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ val viewMessagesModule =
ViewMessagesPresenter(
windowScope = get<ChatroomWindow>().windowScope,
dispatchers = get(),
read = get<ChatRepositories>().readMessages(),
read = get(),
channel = get<ChatRepositories>().subscribeMessages(),
)
}
scoped { DisplayableMessages(get()) }
}
}
17 changes: 5 additions & 12 deletions src/main/kotlin/chatroom/viewmessages/ViewMessagesPresenter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<MessageResult>,
private val read: DisplayableMessages,
private val channel: SubscribeChatRepository,
dispatchers: RokyDispatchers,
) : Presenter<ViewMessagesView>(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()
}
Expand Down
87 changes: 87 additions & 0 deletions src/test/kotlin/chatroom/viewmessages/DisplayableMessagesTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package chatroom.viewmessages

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<ProfileResult>
private lateinit var messages: ReadChatRepository<MessageResult>
private lateinit var displayableMessages: DisplayableMessages

@BeforeEach
fun setUp() {
users =
mockk<ReadChatRepository<ProfileResult>>().also {
every { it.latest() } returns
ProfileResult.ok(
emptyMap(),
)
}
messages = mockk<ReadChatRepository<MessageResult>>().also { every { it.observe() } returns flowOf() }
val repository =
mockk<ChatRepositories>().also {
every { it.readProfiles() } returns users
every { it.readMessages() } returns messages
}
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<String>()
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<String>()
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<String>()
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",
)
}
}
36 changes: 6 additions & 30 deletions src/test/kotlin/chatroom/viewmessages/ViewMessagesPresenterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,7 +19,7 @@ import kotlin.test.assertTrue
@OptIn(ExperimentalCoroutinesApi::class)
class ViewMessagesPresenterTest {
private lateinit var channel: SubscribeChatRepository
private lateinit var read: ReadChatRepository<MessageResult>
private lateinit var read: DisplayableMessages
private lateinit var scope: CoroutineScope
private lateinit var view: ViewMessagesView
private lateinit var presenter: ViewMessagesPresenter
Expand All @@ -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 =
Expand All @@ -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 {
Expand All @@ -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) {
Expand All @@ -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)
Expand Down
Loading