Skip to content
Open
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
69 changes: 65 additions & 4 deletions app/src/androidTest/kotlin/be/scri/helpers/KeyboardTest.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package be.scri.ui
package be.scri.helpers

import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.widget.Button
import androidx.test.ext.junit.runners.AndroidJUnit4
import be.scri.helpers.KeyHandler
import be.scri.helpers.KeyboardBase
import be.scri.helpers.SuggestionHandler
import be.scri.services.GeneralKeyboardIME
import be.scri.services.GeneralKeyboardIME.ScribeState
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

private enum class EnterKeyExpectation {
PERFORM_ACTION,
COMMIT_NEWLINE,
DEFERRED_TO_IME,
}

/** this repreesents a single test case for the parameterized Enter key test. */
private data class EnterKeyTest(
val imeAction: Int,
val scribeState: ScribeState,
val expectation: EnterKeyExpectation,
)

// Unit tests for KeyHandler
@RunWith(AndroidJUnit4::class)
class KeyboardTest {
private lateinit var mockIME: GeneralKeyboardIME
Expand All @@ -35,6 +48,7 @@ class KeyboardTest {
every { mockIME.keyboard } returns mockk(relaxed = true)
every { mockIME.currentState } returns ScribeState.IDLE
every { mockIME.language } returns "German"
every { mockIME.handleKeycodeEnter() } returns Unit

keyHandler = KeyHandler(mockIME)
suggestionHandler = SuggestionHandler(mockIME)
Expand Down Expand Up @@ -74,6 +88,53 @@ class KeyboardTest {
verify(exactly = 1) { mockInputConnection.deleteSurroundingText(1, 0) }
}

@Test
fun testEnterKeyBehavior_Parameterized() {
val testCases =
listOf(
EnterKeyTest(EditorInfo.IME_ACTION_NONE, ScribeState.IDLE, EnterKeyExpectation.COMMIT_NEWLINE),
EnterKeyTest(EditorInfo.IME_ACTION_SEARCH, ScribeState.IDLE, EnterKeyExpectation.PERFORM_ACTION),
EnterKeyTest(EditorInfo.IME_ACTION_SEND, ScribeState.IDLE, EnterKeyExpectation.PERFORM_ACTION),
EnterKeyTest(EditorInfo.IME_ACTION_DONE, ScribeState.IDLE, EnterKeyExpectation.PERFORM_ACTION),
EnterKeyTest(EditorInfo.IME_ACTION_SEND, ScribeState.TRANSLATE, EnterKeyExpectation.DEFERRED_TO_IME),
EnterKeyTest(EditorInfo.IME_ACTION_NONE, ScribeState.CONJUGATE, EnterKeyExpectation.DEFERRED_TO_IME),
EnterKeyTest(EditorInfo.IME_ACTION_SEARCH, ScribeState.PLURAL, EnterKeyExpectation.DEFERRED_TO_IME),
)

testCases.forEach { case ->
every { mockIME.currentInputEditorInfo } returns EditorInfo().apply { imeOptions = case.imeAction }
every { mockIME.currentState } returns case.scribeState

keyHandler.handleKey(KeyboardBase.KEYCODE_ENTER, "en")

when (case.expectation) {
EnterKeyExpectation.PERFORM_ACTION -> {
verify(exactly = 1) { mockInputConnection.performEditorAction(case.imeAction) }
verify(exactly = 0) { mockInputConnection.commitText(any(), any()) }
verify(exactly = 0) { mockIME.handleKeycodeEnter() }
}
EnterKeyExpectation.COMMIT_NEWLINE -> {
verify(exactly = 1) { mockInputConnection.commitText("\n", 1) }
verify(exactly = 0) { mockInputConnection.performEditorAction(any()) }
verify(exactly = 0) { mockIME.handleKeycodeEnter() }
}
EnterKeyExpectation.DEFERRED_TO_IME -> {
// ensures the custom command logic takes precedence
verify(exactly = 1) { mockIME.handleKeycodeEnter() }
verify(exactly = 0) { mockInputConnection.performEditorAction(any()) }
verify(exactly = 0) { mockInputConnection.commitText(any(), any()) }
}
}

// cleans up mocks for the next iteration in the loop
clearMocks(mockInputConnection, mockIME)
every { mockIME.currentInputConnection } returns mockInputConnection
// fix: restores the mock using returns Unit
every { mockIME.handleKeycodeEnter() } returns Unit
every { mockIME.keyboard } returns mockk(relaxed = true)
}
}

@Test
fun processSuggestions() {
every { mockIME.findGenderForLastWord(any(), "in") } returns listOf("Neuter")
Expand Down
32 changes: 31 additions & 1 deletion app/src/main/java/be/scri/helpers/KeyHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package be.scri.helpers

import android.content.Context
import android.util.Log
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import be.scri.services.GeneralKeyboardIME
import be.scri.services.GeneralKeyboardIME.ScribeState
Expand Down Expand Up @@ -235,7 +236,36 @@ class KeyHandler(
* editor action) to the main IME class.
*/
private fun handleEnterKey() {
ime.handleKeycodeEnter()
// step 1: checks if the keyboard is in a command state (not IDLE).
if (ime.currentState != ScribeState.IDLE) {
// reintorducing the call to the state-aware logic for command execution.
ime.handleKeycodeEnter()
return
}

// step 2: if in IDLE state, proceed with standard IME action/newline logic.
val inputConnection = ime.currentInputConnection
val editorInfo = ime.currentInputEditorInfo

if (inputConnection != null && editorInfo != null) {
val imeAction = editorInfo.imeOptions and EditorInfo.IME_MASK_ACTION

when (imeAction) {
EditorInfo.IME_ACTION_GO,
EditorInfo.IME_ACTION_SEARCH,
EditorInfo.IME_ACTION_SEND,
EditorInfo.IME_ACTION_NEXT,
EditorInfo.IME_ACTION_DONE,
-> {
// performs the editor action
inputConnection.performEditorAction(imeAction)
}
else -> {
// default behaviour for inserting a new line
inputConnection.commitText("\n", 1)
}
}
}
}

/**
Expand Down
Loading