Skip to content

Commit

Permalink
Communication: Fix Tagging other Users does not work (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
FelberMartin authored Nov 14, 2024
1 parent 187c979 commit 2f2d1e9
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -417,12 +417,9 @@ internal open class ConversationViewModel(
) { authToken, serverUrl ->
retryOnInternet(networkStatusProvider.currentNetworkStatus) {
conversationService
.searchForPotentialCommunicationParticipants(
.searchForCourseMembers(
courseId = metisContext.courseId,
query = query,
includeStudents = true,
includeTutors = true,
includeInstructors = true,
authToken = authToken,
serverUrl = serverUrl
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import de.tum.informatics.www1.artemis.native_app.core.ui.markdown.MarkdownText
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.R


const val TEST_TAG_MARKDOWN_TEXTFIELD = "TEST_TAG_MARKDOWN_TEXTFIELD"

/**
* @param sendButton composable centered vertically right to the text field.
*/
Expand Down Expand Up @@ -92,7 +96,8 @@ internal fun MarkdownTextField(
onFocusLost()
hadFocus = false
}
},
}
.testTag(TEST_TAG_MARKDOWN_TEXTFIELD),
value = textFieldValue,
onValueChange = onTextChanged,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import kotlin.time.Duration.Companion.seconds
internal const val TEST_TAG_CAN_CREATE_REPLY = "TEST_TAG_CAN_CREATE_REPLY"
internal const val TEST_TAG_REPLY_TEXT_FIELD = "TEST_TAG_REPLY_TEXT_FIELD"
internal const val TEST_TAG_REPLY_SEND_BUTTON = "TEST_TAG_REPLY_SEND_BUTTON"
internal const val TEST_TAG_UNFOCUSED_TEXT_FIELD = "TEST_TAG_UNFOCUSED_TEXT_FIED"

private const val DisabledContentAlpha = 0.75f

Expand Down Expand Up @@ -443,7 +444,8 @@ private fun UnfocusedPreviewReplyTextField(onRequestShowTextField: () -> Unit, t
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onRequestShowTextField)
.padding(horizontal = 16.dp),
.padding(horizontal = 16.dp)
.testTag(TEST_TAG_UNFOCUSED_TEXT_FIELD),
verticalAlignment = Alignment.CenterVertically
) {
Text(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package de.tum.informatics.www1.artemis.native_app.feature.metis.conversation

import de.tum.informatics.www1.artemis.native_app.core.common.test.EndToEndTest
import de.tum.informatics.www1.artemis.native_app.core.common.test.DefaultTestTimeoutMillis
import de.tum.informatics.www1.artemis.native_app.core.test.test_setup.DefaultTimeoutMillis
import de.tum.informatics.www1.artemis.native_app.core.common.test.testServerUrl
import de.tum.informatics.www1.artemis.native_app.core.model.account.User
import de.tum.informatics.www1.artemis.native_app.feature.login.test.user1Username
import de.tum.informatics.www1.artemis.native_app.feature.login.test.user2Username
import de.tum.informatics.www1.artemis.native_app.feature.login.test.user3Username
import de.tum.informatics.www1.artemis.native_app.feature.metistest.ConversationBaseTest
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.experimental.categories.Category
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.util.Logger
import kotlin.test.assertTrue
import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.milliseconds


@Category(EndToEndTest::class)
@RunWith(RobolectricTestRunner::class)
class ConversationAutoCompletionE2eTest : ConversationBaseTest() {

@Test(timeout = DefaultTestTimeoutMillis)
fun `test GIVEN users are registered in a course WHEN requesting auto complete users THEN the registered users are returned`() {
val users = listOf(
User(username = user1Username),
User(username = user2Username),
User(username = user3Username)
)

runTest(timeout = DefaultTimeoutMillis.milliseconds) {

val typedText = "user"
val autoCompleteSuggestions = conversationService.searchForCourseMembers(
courseId = course.id!!,
query = typedText,
authToken = accessToken,
serverUrl = testServerUrl
).orThrow("Could not get auto-complete suggestions")

Logger.info("Auto-complete suggestions: $autoCompleteSuggestions")

assertEquals(users.size, autoCompleteSuggestions.size)

users.forEach { user ->
assertTrue(
autoCompleteSuggestions.any { it.username == user.username },
"Auto-complete suggestions do not contain user $user"
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.reply

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.text.input.TextFieldValue
import androidx.test.ext.junit.runners.AndroidJUnit4
import de.tum.informatics.www1.artemis.native_app.core.data.DataState
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.R
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ReplyTextFieldUiTest {

@get:Rule
val composeTestRule = createComposeRule()

private val autoCompleteHints = listOf(
AutoCompleteCategory(
R.string.markdown_textfield_autocomplete_category_users, listOf(
AutoCompleteHint("User1", "<User1>", "1"),
AutoCompleteHint("User2", "<User2>", "2"),
AutoCompleteHint("User3", "<User3>", "3"),
))
)

private val hintProviderStub = object : ReplyAutoCompleteHintProvider {
override val legalTagChars: List<Char> = listOf('@')
override fun produceAutoCompleteHints(tagChar: Char, query: String): Flow<DataState<List<AutoCompleteCategory>>> {
return flowOf(DataState.Success(autoCompleteHints))
}
}

@Before
fun setUp() {
composeTestRule.setContent {
CompositionLocalProvider(LocalReplyAutoCompleteHintProvider provides hintProviderStub) {
val text = remember { mutableStateOf(TextFieldValue()) }

ReplyTextField(
modifier = Modifier.fillMaxSize(),
replyMode = ReplyMode.NewMessage(
text,
onUpdateTextUpstream = { text.value = it }
) {
CompletableDeferred()
},
updateFailureState = {},
title = "TestChat"
)
}
}

// Click the unfocused textField to focus and expand the textField
composeTestRule.onNodeWithTag(TEST_TAG_UNFOCUSED_TEXT_FIELD).performClick()
}

@Test
fun `test GIVEN an empty reply textField WHEN doing nothing THEN the autoCompletion dialog is hidden`() {
composeTestRule.assertAllAutoCompletionHintsHidden()
}

@Test
fun `test GIVEN an empty reply textField WHEN entering the tag character @ THEN a list of autoCompletionHints for users shows`() {
composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).performTextInput("@")
composeTestRule.assertAllAutoCompletionHintsShown()
}

@Test
fun `test GIVEN the autoCompletion dialog WHEN clicking an entry THEN the replacement is inserted into the textField and the dialog is hidden`() {
composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).performTextInput("@")

composeTestRule.onNodeWithText("User1").performClick()

composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).assertTextEquals("<User1>")
composeTestRule.assertAllAutoCompletionHintsHidden()
}

@Test
fun `test GIVEN the textField WHEN entering a non-tag character THEN the autoCompletion dialog is hidden`() {
composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).performTextInput("a")
composeTestRule.assertAllAutoCompletionHintsHidden()
}

@Test
fun `test GIVEN the autoCompletion dialog WHEN removing the tag character @ THEN the autoCompletion dialog is hidden`() {
composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).performTextInput("@")
composeTestRule.assertAllAutoCompletionHintsShown()

composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).performTextClearance()
composeTestRule.assertAllAutoCompletionHintsHidden()
}

@Test
fun `test GIVEN the autoCompletion has been performed WHEN entering the tag character again THEN the autoCompletion dialog shows again`() {
composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).performTextInput("@")
composeTestRule.onNodeWithText("User1").performClick()
composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).assertTextEquals("<User1>")
composeTestRule.assertAllAutoCompletionHintsHidden()

composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).performTextInput("@")
composeTestRule.assertAllAutoCompletionHintsShown()
}

@Test
fun `test GIVEN the textField WHEN entering a first and surname separated by a single whitespace THEN the dialog shows`() {
composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).performTextInput("@FirstName SurName")
composeTestRule.assertAllAutoCompletionHintsShown()
}

@Test
fun `test GIVEN the textField WHEN entering a second whitespace THEN the dialog is hidden`() {
composeTestRule.onNodeWithTag(TEST_TAG_MARKDOWN_TEXTFIELD).performTextInput("@FirstName SurName ")
composeTestRule.assertAllAutoCompletionHintsHidden()
}



private fun ComposeContentTestRule.assertAllAutoCompletionHintsHidden() {
onNodeWithText("User1").assertDoesNotExist()
onNodeWithText("User2").assertDoesNotExist()
onNodeWithText("User3").assertDoesNotExist()
}

private fun ComposeContentTestRule.assertAllAutoCompletionHintsShown() {
onNodeWithText("User1").assertExists()
onNodeWithText("User2").assertExists()
onNodeWithText("User3").assertExists()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ open class ConversationServiceStub(
serverUrl: String
): NetworkResponse<List<User>> = NetworkResponse.Response(emptyList())

override suspend fun searchForCourseMembers(
courseId: Long,
query: String,
authToken: String,
serverUrl: String
): NetworkResponse<List<User>> = NetworkResponse.Response(emptyList())

override suspend fun createOneToOneConversation(
courseId: Long,
partner: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ interface ConversationService {
serverUrl: String
): NetworkResponse<List<User>>

suspend fun searchForCourseMembers(
courseId: Long,
query: String,
authToken: String,
serverUrl: String
): NetworkResponse<List<User>>

suspend fun createOneToOneConversation(
courseId: Long,
partner: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ class ConversationServiceImpl(private val ktorProvider: KtorProvider) : Conversa
}
}

override suspend fun searchForCourseMembers(
courseId: Long,
query: String,
authToken: String,
serverUrl: String
): NetworkResponse<List<User>> {
return performNetworkCall {
ktorProvider.ktorClient.get(serverUrl) {
url {
appendPathSegments("api", "courses", courseId.toString(), "members", "search")

parameter("loginOrName", query)
}

cookieAuth(authToken)
}.body()
}
}

override suspend fun createGroupChat(
courseId: Long,
groupMembers: List<String>,
Expand Down

0 comments on commit 2f2d1e9

Please sign in to comment.