From 5de4cfeaa74e0493d9d4e236dc283f01b5d99f9e Mon Sep 17 00:00:00 2001 From: Chris Witt Date: Mon, 21 Sep 2026 13:37:23 -0400 Subject: [PATCH 1/7] CW - Fixing non-null assertions in EmojiHelper and RecentEmojiHelper. --- .../java/be/scri/helpers/RecentEmojiHelper.kt | 22 +-- .../main/java/be/scri/helpers/EmojiHelper.kt | 18 +-- .../kotlin/be/scri/helpers/EmojiHelperTest.kt | 131 ++++++++++++++++++ .../be/scri/helpers/RecentEmojiHelperTest.kt | 95 +++++++++++++ 4 files changed, 249 insertions(+), 17 deletions(-) create mode 100644 app/src/test/kotlin/be/scri/helpers/EmojiHelperTest.kt create mode 100644 app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt diff --git a/app/src/keyboards/java/be/scri/helpers/RecentEmojiHelper.kt b/app/src/keyboards/java/be/scri/helpers/RecentEmojiHelper.kt index ad0dc3e78..00cf42472 100644 --- a/app/src/keyboards/java/be/scri/helpers/RecentEmojiHelper.kt +++ b/app/src/keyboards/java/be/scri/helpers/RecentEmojiHelper.kt @@ -12,19 +12,23 @@ fun recordRecentEmoji( emoji: String, ) { val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - val current = - prefs - .getString(KEY_RECENT, "")!! - .split(",") + + val recentEmojisPrefs = prefs.getString(KEY_RECENT, "") + + recentEmojisPrefs?.let { recentEmojis -> + val current = recentEmojis.split(",") .filter { it.isNotBlank() } .toMutableList() - current.remove(emoji) - current.add(0, emoji) - while (current.size > MAX_RECENT) current.removeAt(current.lastIndex) - prefs.edit().putString(KEY_RECENT, current.joinToString(",")).apply() + + current.remove(emoji) + current.add(0, emoji) + while (current.size > MAX_RECENT) current.removeAt(current.lastIndex) + prefs.edit().putString(KEY_RECENT, current.joinToString(",")).apply() + } } fun getRecentEmojis(context: Context): List { val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - return prefs.getString(KEY_RECENT, "")!!.split(",").filter { it.isNotBlank() } + val recentEmojisPrefs = prefs.getString(KEY_RECENT, "") ?: "" + return recentEmojisPrefs.split(",").filter { it.isNotBlank() } } diff --git a/app/src/main/java/be/scri/helpers/EmojiHelper.kt b/app/src/main/java/be/scri/helpers/EmojiHelper.kt index fca1b156b..44487ed5b 100644 --- a/app/src/main/java/be/scri/helpers/EmojiHelper.kt +++ b/app/src/main/java/be/scri/helpers/EmojiHelper.kt @@ -20,16 +20,18 @@ fun parseRawEmojiSpecsFile( path: String, ): MutableList { val emojis = mutableListOf() - var emojiEditorList: MutableList? = null + var emojiEditorList = mutableListOf() var category: String? = null fun commitEmojiEditorList() { - emojiEditorList?.let { - val base = it.first() - val variants = it.drop(1) - emojis.add(EmojiData(category ?: "none", base, variants)) + if (emojiEditorList.isNotEmpty()) { + emojiEditorList.let { + val base = it.first() + val variants = it.drop(1) + emojis.add(EmojiData(category ?: "none", base, variants)) + } } - emojiEditorList = null + emojiEditorList = emptyList().toMutableList() } context.assets.open(path).bufferedReader().useLines { lines -> @@ -48,8 +50,8 @@ fun parseRawEmojiSpecsFile( val data = line.split(";") if (data.size == 3) { val emoji = data[0].trim() - if (emojiEditorList != null) { - emojiEditorList!!.add(emoji) + if (emojiEditorList.isNotEmpty()) { + emojiEditorList.add(emoji) } else { emojiEditorList = mutableListOf(emoji) } diff --git a/app/src/test/kotlin/be/scri/helpers/EmojiHelperTest.kt b/app/src/test/kotlin/be/scri/helpers/EmojiHelperTest.kt new file mode 100644 index 000000000..5f7b9d43b --- /dev/null +++ b/app/src/test/kotlin/be/scri/helpers/EmojiHelperTest.kt @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package be.scri.helpers + +import android.content.Context +import android.content.res.AssetManager +import be.scri.R +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.io.ByteArrayInputStream + +class EmojiHelperTest { + + @Test + fun parseRawEmojiSpecsFile_validFile_parsesCorrectly() { + val context = mockk() + val assetManager = mockk() + val specContent = """ + # Comment line + [smileys_emotion] + 😀;; + 😃;; + + [people_body] + 👋;; + 👋🏻;; + 👋🏼;; + """.trimIndent() + val inputStream = ByteArrayInputStream(specContent.toByteArray()) + + every { context.assets } returns assetManager + every { assetManager.open("emoji_spec.txt") } returns inputStream + + val result = parseRawEmojiSpecsFile(context, "emoji_spec.txt") + + assertEquals(3, result.size) + + assertEquals("smileys_emotion", result[0].category) + assertEquals("😀", result[0].emoji) + assertEquals(0, result[0].variants.size) + + assertEquals("smileys_emotion", result[1].category) + assertEquals("😃", result[1].emoji) + assertEquals(0, result[1].variants.size) + + assertEquals("people_body", result[2].category) + assertEquals("👋", result[2].emoji) + assertEquals(listOf("👋🏻", "👋🏼"), result[2].variants) + } + + @Test + fun parseRawEmojiSpecsFile_emptyAndInvalidLines_ignoresThem() { + val context = mockk() + val assetManager = mockk() + val specContent = """ + [cat1] + + # comment + emoji1;; + + invalid_line + emoji2;; + """.trimIndent() + val inputStream = ByteArrayInputStream(specContent.toByteArray()) + + every { context.assets } returns assetManager + every { assetManager.open("emoji_spec.txt") } returns inputStream + + val result = parseRawEmojiSpecsFile(context, "emoji_spec.txt") + + assertEquals(2, result.size) + assertEquals("emoji1", result[0].emoji) + assertEquals("emoji2", result[1].emoji) + } + + @Test + fun parseRawEmojiSpecsFile_noCategory_defaultsToNone() { + val context = mockk() + val assetManager = mockk() + val specContent = """ + emoji1;; + """.trimIndent() + val inputStream = ByteArrayInputStream(specContent.toByteArray()) + + every { context.assets } returns assetManager + every { assetManager.open("emoji_spec.txt") } returns inputStream + + val result = parseRawEmojiSpecsFile(context, "emoji_spec.txt") + + assertEquals(1, result.size) + assertEquals("none", result[0].category) + assertEquals("emoji1", result[0].emoji) + } + + @Test + fun getCategoryIconRes_knownCategories_returnsCorrectIcon() { + assertEquals(R.drawable.ic_emoji_smileys, getCategoryIconRes("smileys_emotion")) + assertEquals(R.drawable.ic_emoji_people, getCategoryIconRes("people_body")) + assertEquals(R.drawable.ic_emoji_animals, getCategoryIconRes("animals_nature")) + assertEquals(R.drawable.ic_emoji_food, getCategoryIconRes("food_drink")) + assertEquals(R.drawable.ic_emoji_travel, getCategoryIconRes("travel_places")) + assertEquals(R.drawable.ic_emoji_activities, getCategoryIconRes("activities")) + assertEquals(R.drawable.ic_emoji_objects, getCategoryIconRes("objects")) + assertEquals(R.drawable.ic_emoji_symbols, getCategoryIconRes("symbols")) + assertEquals(R.drawable.ic_emoji_flags, getCategoryIconRes("flags")) + assertEquals(R.drawable.counter_clockwise_icon, getCategoryIconRes("recently_used")) + } + + @Test + fun getCategoryIconRes_unknownCategory_returnsDefaultIcon() { + assertEquals(R.drawable.ic_emoji_vector, getCategoryIconRes("unknown_category")) + } + + @Test + fun parseRawEmojiSpecsFile_firstLineIsVariant_treatsAsBase() { + val context = mockk() + val assetManager = mockk() + val specContent = "\tvariant1;;\n" + val inputStream = ByteArrayInputStream(specContent.toByteArray()) + + every { context.assets } returns assetManager + every { assetManager.open("emoji_spec.txt") } returns inputStream + + val result = parseRawEmojiSpecsFile(context, "emoji_spec.txt") + + assertEquals(1, result.size) + assertEquals("variant1", result[0].emoji) + assertEquals(0, result[0].variants.size) + } +} diff --git a/app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt b/app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt new file mode 100644 index 000000000..b410bc303 --- /dev/null +++ b/app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt @@ -0,0 +1,95 @@ +package be.scri.helpers + +import android.content.Context +import android.content.SharedPreferences +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class RecentEmojiHelperTest { + + @Test + fun getRecentEmojis_returnsCorrectList_whenRecentEmojiListExists() { + val context = mockk() + val mockPreferences = mockk() + + every { mockPreferences.getString("recent_emoji_list", "") } returns "emoji1,emoji2" + every { context.getSharedPreferences("recent_emojis", Context.MODE_PRIVATE) } returns mockPreferences + + assertEquals(listOf("emoji1", "emoji2"), getRecentEmojis(context)) + } + + @Test + fun getRecentEmojis_returnsEmptyList_whenRecentEmojiListDoesNotExist() { + val context = mockk() + val mockPreferences = mockk() + + every { mockPreferences.getString("recent_emoji_list", "") } returns "" + every { context.getSharedPreferences("recent_emojis", Context.MODE_PRIVATE) } returns mockPreferences + + assertEquals(emptyList(), getRecentEmojis(context)) + } + + @Test + fun getRecentEmojis_returnsCorrectList_whenRecentEmojiListIsNull() { + val context = mockk() + val mockPreferences = mockk() + + every { mockPreferences.getString("recent_emoji_list", "") } returns null + every { context.getSharedPreferences("recent_emojis", Context.MODE_PRIVATE) } returns mockPreferences + + assertEquals(emptyList(), getRecentEmojis(context)) + } + + @Test + fun recordRecentEmojis_addsEmojiToRecentList_whenEmojiDoesNotExist() { + val context = mockk() + val mockPreferences = mockk() + val mockEditor = mockk(relaxed = true) + + every { mockPreferences.getString("recent_emoji_list", "") } returns "emoji1,emoji2" + every { mockPreferences.edit() } returns mockEditor + every { context.getSharedPreferences("recent_emojis", Context.MODE_PRIVATE) } returns mockPreferences + + recordRecentEmoji(context, "emoji3") + + verify { mockEditor.putString("recent_emoji_list", "emoji3,emoji1,emoji2") } + } + + @Test + fun recordRecentEmojis_removesOldestEmoji_whenRecentListIsFull() { + val context = mockk() + val mockPreferences = mockk() + val mockEditor = mockk(relaxed = true) + + // Create a list of 30 emojis: "e1,e2,...,e30" + val initialList = (1..30).joinToString(",") { "e$it" } + // Adding "new" should push "e30" out. Result: "new,e1,e2,...,e29" + val expectedList = "new," + (1..29).joinToString(",") { "e$it" } + + every { mockPreferences.getString("recent_emoji_list", "") } returns initialList + every { mockPreferences.edit() } returns mockEditor + every { context.getSharedPreferences("recent_emojis", Context.MODE_PRIVATE) } returns mockPreferences + + recordRecentEmoji(context, "new") + + verify { mockEditor.putString("recent_emoji_list", expectedList) } + } + + @Test + fun recordRecentEmojis_doesNotAddDuplicateEmoji_whenRecentEmojisNull() { + val context = mockk() + val mockPreferences = mockk() + val mockEditor = mockk(relaxed = true) + + every { mockPreferences.getString("recent_emoji_list", "") } returns null + every { mockPreferences.edit() } returns mockEditor + every { context.getSharedPreferences("recent_emojis", Context.MODE_PRIVATE) } returns mockPreferences + + recordRecentEmoji(context, "emoji1") + + verify(exactly = 0) { mockEditor.putString(any(), any()) } + } +} From c0a4e9f3c81b6e4beb71932fa9a036b9d7e7d1f7 Mon Sep 17 00:00:00 2001 From: Chris Witt Date: Mon, 21 Sep 2026 21:40:36 -0400 Subject: [PATCH 2/7] CW - Fixing non-null assertions in AutocompletionHandler. --- .../be/scri/helpers/AutocompletionHandler.kt | 6 +- .../scri/helpers/AutocompletionHandlerTest.kt | 150 ++++++++++++++++++ 2 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt diff --git a/app/src/keyboards/java/be/scri/helpers/AutocompletionHandler.kt b/app/src/keyboards/java/be/scri/helpers/AutocompletionHandler.kt index f1f9f4142..1de61c9c1 100644 --- a/app/src/keyboards/java/be/scri/helpers/AutocompletionHandler.kt +++ b/app/src/keyboards/java/be/scri/helpers/AutocompletionHandler.kt @@ -13,8 +13,8 @@ import be.scri.models.ScribeState */ class AutocompletionHandler( private val ime: KeyboardIMEContext, + private val handler: Handler = Handler(Looper.getMainLooper()), ) { - private val handler = Handler(Looper.getMainLooper()) private var autocompleteRunnable: Runnable? = null companion object { @@ -62,9 +62,7 @@ class AutocompletionHandler( val completions = ime.getAutocompletions(currentWord, previousWord, limit = MAX_COMPLETIONS + 1) ime.updateAutocompleteCompletions(buildCompletions(currentWord, completions)) - } - - handler.postDelayed(autocompleteRunnable!!, AUTOCOMPLETE_DELAY_MS) + }.also { handler.postDelayed(it, AUTOCOMPLETE_DELAY_MS) } } /** diff --git a/app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt b/app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt new file mode 100644 index 000000000..959dee1ef --- /dev/null +++ b/app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt @@ -0,0 +1,150 @@ +package be.scri.helpers + +import android.os.Handler +import android.os.Looper +import be.scri.models.ScribeState +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class AutocompletionHandlerTest { + + private lateinit var looper: Looper + private lateinit var handler: Handler + + @MockK(relaxed = true) + private lateinit var ime: KeyboardIMEContext + private lateinit var autocompletionHandler: AutocompletionHandler + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this) + looper = mockk(relaxed = true) + handler = mockk(relaxed = true) + autocompletionHandler = AutocompletionHandler(ime, handler) + } + + @Test + fun buildCompletions_completionsMatchTypedWord_emptyResponse() { + val typedWord = "word" + val completions = listOf("word", "word") + + val result = AutocompletionHandler.buildCompletions(typedWord, completions) + + assert(result.isEmpty()) + } + + @Test + fun buildCompletions_completionsContainTypedWord_removesIt() { + val typedWord = "word" + val completions = listOf("word", "wordy") + + val result = AutocompletionHandler.buildCompletions(typedWord, completions) + + assert(result.size == 1) + assert(result[0] == "wordy") + } + + @Test + fun buildCompletions_completionsContainManyMore_returnsOnlyTwo() { + val typedWord = "word" + val completions = List(10) { "word$it" } + + val result = AutocompletionHandler.buildCompletions(typedWord, completions) + + assert(result.size == 2) + assert(result[0] == "word0") + assert(result[1] == "word1") + } + + @Test + fun processAutocomplete_emptyWord_clearsAutocomplete() { + every { ime.currentState } returns ScribeState.IDLE + + autocompletionHandler.processAutocomplete("") + + verify { ime.clearAutocomplete() } + } + + @Test + fun processAutocomplete_inWrongState_clearsAutocomplete() { + every { ime.currentState } returns ScribeState.PLURAL + + autocompletionHandler.processAutocomplete("word") + + verify { ime.clearAutocomplete() } + } + + @Test + fun processAutocomplete_withWord_updatesTypedWordSuggestion() { + val runnableSlot = slot() + every { handler.postDelayed(capture(runnableSlot), any()) } returns true + + every { ime.currentState } returns ScribeState.IDLE + + val currentWord = "word" + autocompletionHandler.processAutocomplete(currentWord) + + runnableSlot.captured.run() + verify { ime.updateTypedWordSuggestion(currentWord) } + } + + @Test + fun processAutocomplete_withWord_updatesAutocompleteCompletions() { + val runnableSlot = slot() + every { handler.postDelayed(capture(runnableSlot), any()) } returns true + + every { ime.currentState } returns ScribeState.IDLE + + val currentWord = "word" + autocompletionHandler.processAutocomplete(currentWord) + + runnableSlot.captured.run() + + verify { ime.updateAutocompleteCompletions(any()) } + } + + @Test + fun processAutocomplete_calledMultipleTimes_removesPreviousCallbacks() { + every { ime.currentState } returns ScribeState.IDLE + + autocompletionHandler.processAutocomplete("first") + + autocompletionHandler.processAutocomplete("second") + + verify(exactly = 1) { handler.removeCallbacks(any()) } + } + + @Test + fun clearAutocomplete_cancelsPendingTaskAndClearsIme() { + every { ime.currentState } returns ScribeState.IDLE + + autocompletionHandler.processAutocomplete("word") + + autocompletionHandler.clearAutocomplete() + + verify { handler.removeCallbacks(any()) } + verify { ime.clearAutocomplete() } + } + + @Test + fun processAutocomplete_stateChangesBeforeRunnableExecutes_abortsExecution() { + val runnableSlot = slot() + every { handler.postDelayed(capture(runnableSlot), any()) } returns true + + every { ime.currentState } returns ScribeState.IDLE + autocompletionHandler.processAutocomplete("word") + + every { ime.currentState } returns ScribeState.PLURAL + + runnableSlot.captured.run() + + verify(exactly = 0) { ime.getAutocompletions(any(), any(), any()) } + verify(exactly = 0) { ime.updateAutocompleteCompletions(any()) } + } +} From 8bc32d882c66b8c5dccc1434b5069642b863874e Mon Sep 17 00:00:00 2001 From: Chris Witt Date: Mon, 21 Sep 2026 21:41:43 -0400 Subject: [PATCH 3/7] CW - Adding license to test files. --- app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt | 1 + app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt b/app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt index 959dee1ef..701219b2e 100644 --- a/app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt +++ b/app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0-or-later package be.scri.helpers import android.os.Handler diff --git a/app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt b/app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt index b410bc303..1d1f7949d 100644 --- a/app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt +++ b/app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0-or-later package be.scri.helpers import android.content.Context From b184454f5e78fb3e35344821c437e2d6d96dc77a Mon Sep 17 00:00:00 2001 From: Chris Witt Date: Wed, 23 Sep 2026 21:13:50 -0400 Subject: [PATCH 4/7] CW - Fixing non-null assertions in HintUtils. --- app/build.gradle.kts | 1 + .../java/be/scri/helpers/ui/HintUtils.kt | 3 +- .../be/scri/helpers/ui/HintUtilsTest.kt | 74 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 app/src/test/kotlin/be/scri/helpers/ui/HintUtilsTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 69f8b958c..22356668a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -287,6 +287,7 @@ dependencies { // Unit Testing // ========================== testImplementation("org.junit.jupiter:junit-jupiter-api:$junit5Version") + testImplementation("org.junit.jupiter:junit-jupiter-params:${junit5Version}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit5Version") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:$junit5Version") testImplementation("io.mockk:mockk:$mockkVersion") diff --git a/app/src/keyboards/java/be/scri/helpers/ui/HintUtils.kt b/app/src/keyboards/java/be/scri/helpers/ui/HintUtils.kt index 5210e371f..1bf30b910 100644 --- a/app/src/keyboards/java/be/scri/helpers/ui/HintUtils.kt +++ b/app/src/keyboards/java/be/scri/helpers/ui/HintUtils.kt @@ -12,6 +12,7 @@ import be.scri.helpers.portuguese.PTInterfaceVariables import be.scri.helpers.russian.RUInterfaceVariables import be.scri.helpers.spanish.ESInterfaceVariables import be.scri.helpers.swedish.SVInterfaceVariables +import be.scri.latin.utils.Log import be.scri.models.ScribeState import kotlin.collections.get @@ -320,7 +321,7 @@ object HintUtils { ScribeState.TRANSLATE -> getTranslationPrompt(language, context) ScribeState.CONJUGATE -> getConjugationPrompt(language) ScribeState.PLURAL -> getPluralPrompt(language) - ScribeState.SELECT_VERB_CONJUNCTION -> text!! + ScribeState.SELECT_VERB_CONJUNCTION -> text ?: "" else -> "" } diff --git a/app/src/test/kotlin/be/scri/helpers/ui/HintUtilsTest.kt b/app/src/test/kotlin/be/scri/helpers/ui/HintUtilsTest.kt new file mode 100644 index 000000000..795c4deae --- /dev/null +++ b/app/src/test/kotlin/be/scri/helpers/ui/HintUtilsTest.kt @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package be.scri.helpers.ui + +import android.content.Context +import be.scri.helpers.english.ENInterfaceVariables +import be.scri.helpers.french.FRInterfaceVariables +import be.scri.helpers.german.DEInterfaceVariables +import be.scri.helpers.italian.ITInterfaceVariables +import be.scri.helpers.portuguese.PTInterfaceVariables +import be.scri.helpers.russian.RUInterfaceVariables +import be.scri.helpers.spanish.ESInterfaceVariables +import be.scri.helpers.swedish.SVInterfaceVariables +import be.scri.models.ScribeState +import io.mockk.MockKAnnotations +import io.mockk.impl.annotations.MockK +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream + +class HintUtilsTest { + + @MockK(relaxed = true) + private lateinit var context: Context + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + } + + @Test + fun getPromptText_textNullAndSelectVerbConjugation_expectEmptyString() { + val promptText = HintUtils.getPromptText(ScribeState.SELECT_VERB_CONJUNCTION, "English", context, null) + assertEquals("", promptText) + } + + @Test + fun getPromptText_textNotNullAndSelectVerbConjugation_expectText() { + val promptText = HintUtils.getPromptText(ScribeState.SELECT_VERB_CONJUNCTION, "English", context, "text") + assertEquals("text", promptText) + } + + @ParameterizedTest + @MethodSource("provideLanguagesAndPluralPrompts") + fun getPromptText_selectPlural_expectPluralPrompt(language: String, expectedPrompt: String) { + val promptText = HintUtils.getPromptText(ScribeState.PLURAL, language, context, null) + assertEquals(expectedPrompt, promptText) + } + + @Test + fun getPromptText_selectPluralAndLanguageIsInvalid_expectEnglishDefault() { + val promptText = HintUtils.getPromptText(ScribeState.PLURAL, "FAKEISH", context, null) + assertEquals("Plural :", promptText) + } + + companion object { + @JvmStatic + fun provideLanguagesAndPluralPrompts(): Stream { + return Stream.of( + Arguments.of("English", ENInterfaceVariables.PLURAL_PROMPT), + Arguments.of("French", FRInterfaceVariables.PLURAL_PROMPT), + Arguments.of("German", DEInterfaceVariables.PLURAL_PROMPT), + Arguments.of("Italian", ITInterfaceVariables.PLURAL_PROMPT), + Arguments.of("Portuguese", PTInterfaceVariables.PLURAL_PROMPT), + Arguments.of("Russian", RUInterfaceVariables.PLURAL_PROMPT), + Arguments.of("Spanish", ESInterfaceVariables.PLURAL_PROMPT), + Arguments.of("Swedish", SVInterfaceVariables.PLURAL_PROMPT), + ) + } + } +} From 7554613c1d9177b65c430758e65cb69d79f776d0 Mon Sep 17 00:00:00 2001 From: Chris Witt Date: Fri, 25 Sep 2026 13:27:57 -0400 Subject: [PATCH 5/7] CW - Fixing class constructor documentation for AutocompletionHandler. --- app/src/keyboards/java/be/scri/helpers/AutocompletionHandler.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/keyboards/java/be/scri/helpers/AutocompletionHandler.kt b/app/src/keyboards/java/be/scri/helpers/AutocompletionHandler.kt index 1de61c9c1..598cfdf30 100644 --- a/app/src/keyboards/java/be/scri/helpers/AutocompletionHandler.kt +++ b/app/src/keyboards/java/be/scri/helpers/AutocompletionHandler.kt @@ -10,6 +10,7 @@ import be.scri.models.ScribeState * Handles autocompletion when user is typing. * * @property ime The [KeyboardIMEContext] instance this handler is associated with. + * @property handler The [Handler] used to schedule autocompletion tasks. */ class AutocompletionHandler( private val ime: KeyboardIMEContext, From eab1dd0a7c1d85e34489f91e82a6543e865af2d1 Mon Sep 17 00:00:00 2001 From: Chris Witt Date: Fri, 25 Sep 2026 13:54:56 -0400 Subject: [PATCH 6/7] CW - Moving tests to correct directory. --- .../kotlin/be/scri/helpers/AutocompletionHandlerTest.kt | 0 .../kotlin/be/scri/helpers/RecentEmojiHelperTest.kt | 0 .../kotlin/be/scri/helpers/ui/HintUtilsTest.kt | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename app/src/{test => testKeyboards}/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt (100%) rename app/src/{test => testKeyboards}/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt (100%) rename app/src/{test => testKeyboards}/kotlin/be/scri/helpers/ui/HintUtilsTest.kt (100%) diff --git a/app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt b/app/src/testKeyboards/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt similarity index 100% rename from app/src/test/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt rename to app/src/testKeyboards/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt diff --git a/app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt b/app/src/testKeyboards/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt similarity index 100% rename from app/src/test/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt rename to app/src/testKeyboards/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt diff --git a/app/src/test/kotlin/be/scri/helpers/ui/HintUtilsTest.kt b/app/src/testKeyboards/kotlin/be/scri/helpers/ui/HintUtilsTest.kt similarity index 100% rename from app/src/test/kotlin/be/scri/helpers/ui/HintUtilsTest.kt rename to app/src/testKeyboards/kotlin/be/scri/helpers/ui/HintUtilsTest.kt From 3a47c8e3a6943aeb4a082fcd6c9731696f63bc77 Mon Sep 17 00:00:00 2001 From: Chris Witt Date: Fri, 25 Sep 2026 16:21:42 -0400 Subject: [PATCH 7/7] CW - Fixing linting issues. --- .../java/be/scri/helpers/RecentEmojiHelper.kt | 8 +++++--- .../java/be/scri/helpers/ui/HintUtils.kt | 1 - .../kotlin/be/scri/helpers/EmojiHelperTest.kt | 16 +++++++++------- .../be/scri/helpers/AutocompletionHandlerTest.kt | 1 - .../be/scri/helpers/RecentEmojiHelperTest.kt | 1 - .../kotlin/be/scri/helpers/ui/HintUtilsTest.kt | 11 ++++++----- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/app/src/keyboards/java/be/scri/helpers/RecentEmojiHelper.kt b/app/src/keyboards/java/be/scri/helpers/RecentEmojiHelper.kt index 00cf42472..029e3fd53 100644 --- a/app/src/keyboards/java/be/scri/helpers/RecentEmojiHelper.kt +++ b/app/src/keyboards/java/be/scri/helpers/RecentEmojiHelper.kt @@ -16,9 +16,11 @@ fun recordRecentEmoji( val recentEmojisPrefs = prefs.getString(KEY_RECENT, "") recentEmojisPrefs?.let { recentEmojis -> - val current = recentEmojis.split(",") - .filter { it.isNotBlank() } - .toMutableList() + val current = + recentEmojis + .split(",") + .filter { it.isNotBlank() } + .toMutableList() current.remove(emoji) current.add(0, emoji) diff --git a/app/src/keyboards/java/be/scri/helpers/ui/HintUtils.kt b/app/src/keyboards/java/be/scri/helpers/ui/HintUtils.kt index 1bf30b910..30189da1a 100644 --- a/app/src/keyboards/java/be/scri/helpers/ui/HintUtils.kt +++ b/app/src/keyboards/java/be/scri/helpers/ui/HintUtils.kt @@ -12,7 +12,6 @@ import be.scri.helpers.portuguese.PTInterfaceVariables import be.scri.helpers.russian.RUInterfaceVariables import be.scri.helpers.spanish.ESInterfaceVariables import be.scri.helpers.swedish.SVInterfaceVariables -import be.scri.latin.utils.Log import be.scri.models.ScribeState import kotlin.collections.get diff --git a/app/src/test/kotlin/be/scri/helpers/EmojiHelperTest.kt b/app/src/test/kotlin/be/scri/helpers/EmojiHelperTest.kt index 5f7b9d43b..35e69099c 100644 --- a/app/src/test/kotlin/be/scri/helpers/EmojiHelperTest.kt +++ b/app/src/test/kotlin/be/scri/helpers/EmojiHelperTest.kt @@ -11,12 +11,12 @@ import org.junit.jupiter.api.Test import java.io.ByteArrayInputStream class EmojiHelperTest { - @Test fun parseRawEmojiSpecsFile_validFile_parsesCorrectly() { val context = mockk() val assetManager = mockk() - val specContent = """ + val specContent = + """ # Comment line [smileys_emotion] 😀;; @@ -26,7 +26,7 @@ class EmojiHelperTest { 👋;; 👋🏻;; 👋🏼;; - """.trimIndent() + """.trimIndent() val inputStream = ByteArrayInputStream(specContent.toByteArray()) every { context.assets } returns assetManager @@ -53,7 +53,8 @@ class EmojiHelperTest { fun parseRawEmojiSpecsFile_emptyAndInvalidLines_ignoresThem() { val context = mockk() val assetManager = mockk() - val specContent = """ + val specContent = + """ [cat1] # comment @@ -61,7 +62,7 @@ class EmojiHelperTest { invalid_line emoji2;; - """.trimIndent() + """.trimIndent() val inputStream = ByteArrayInputStream(specContent.toByteArray()) every { context.assets } returns assetManager @@ -78,9 +79,10 @@ class EmojiHelperTest { fun parseRawEmojiSpecsFile_noCategory_defaultsToNone() { val context = mockk() val assetManager = mockk() - val specContent = """ + val specContent = + """ emoji1;; - """.trimIndent() + """.trimIndent() val inputStream = ByteArrayInputStream(specContent.toByteArray()) every { context.assets } returns assetManager diff --git a/app/src/testKeyboards/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt b/app/src/testKeyboards/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt index 701219b2e..60c3beaf8 100644 --- a/app/src/testKeyboards/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt +++ b/app/src/testKeyboards/kotlin/be/scri/helpers/AutocompletionHandlerTest.kt @@ -14,7 +14,6 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class AutocompletionHandlerTest { - private lateinit var looper: Looper private lateinit var handler: Handler diff --git a/app/src/testKeyboards/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt b/app/src/testKeyboards/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt index 1d1f7949d..6b800959c 100644 --- a/app/src/testKeyboards/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt +++ b/app/src/testKeyboards/kotlin/be/scri/helpers/RecentEmojiHelperTest.kt @@ -10,7 +10,6 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class RecentEmojiHelperTest { - @Test fun getRecentEmojis_returnsCorrectList_whenRecentEmojiListExists() { val context = mockk() diff --git a/app/src/testKeyboards/kotlin/be/scri/helpers/ui/HintUtilsTest.kt b/app/src/testKeyboards/kotlin/be/scri/helpers/ui/HintUtilsTest.kt index 795c4deae..43da78dd4 100644 --- a/app/src/testKeyboards/kotlin/be/scri/helpers/ui/HintUtilsTest.kt +++ b/app/src/testKeyboards/kotlin/be/scri/helpers/ui/HintUtilsTest.kt @@ -22,7 +22,6 @@ import org.junit.jupiter.params.provider.MethodSource import java.util.stream.Stream class HintUtilsTest { - @MockK(relaxed = true) private lateinit var context: Context @@ -45,7 +44,10 @@ class HintUtilsTest { @ParameterizedTest @MethodSource("provideLanguagesAndPluralPrompts") - fun getPromptText_selectPlural_expectPluralPrompt(language: String, expectedPrompt: String) { + fun getPromptText_selectPlural_expectPluralPrompt( + language: String, + expectedPrompt: String, + ) { val promptText = HintUtils.getPromptText(ScribeState.PLURAL, language, context, null) assertEquals(expectedPrompt, promptText) } @@ -58,8 +60,8 @@ class HintUtilsTest { companion object { @JvmStatic - fun provideLanguagesAndPluralPrompts(): Stream { - return Stream.of( + fun provideLanguagesAndPluralPrompts(): Stream = + Stream.of( Arguments.of("English", ENInterfaceVariables.PLURAL_PROMPT), Arguments.of("French", FRInterfaceVariables.PLURAL_PROMPT), Arguments.of("German", DEInterfaceVariables.PLURAL_PROMPT), @@ -69,6 +71,5 @@ class HintUtilsTest { Arguments.of("Spanish", ESInterfaceVariables.PLURAL_PROMPT), Arguments.of("Swedish", SVInterfaceVariables.PLURAL_PROMPT), ) - } } }