From beb32262de079779b627ecd80571869f6385eb83 Mon Sep 17 00:00:00 2001 From: Ashish Yadav <48384865+criticalAY@users.noreply.github.com> Date: Mon, 3 Feb 2025 02:03:22 +0530 Subject: [PATCH] test: unit test for normalizeForSearch - extract method to extension file --- .../anki/browser/CardBrowserViewModel.kt | 8 +--- .../java/com/ichi2/anki/utils/ext/String.kt | 44 +++++++++++++++++++ .../analytics/PreferencesAnalyticsTest.kt | 2 +- .../anki/browser/CardBrowserViewModelTest.kt | 19 ++++++++ 4 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/String.kt diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt index 067ae632ccb7..a350c41595ee 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt @@ -47,6 +47,7 @@ import com.ichi2.anki.model.CardsOrNotes.NOTES import com.ichi2.anki.model.SortType import com.ichi2.anki.pages.CardInfoDestination import com.ichi2.anki.preferences.SharedPreferencesProvider +import com.ichi2.anki.utils.ext.normalizeForSearch import com.ichi2.annotations.NeedsTest import com.ichi2.libanki.Card import com.ichi2.libanki.CardId @@ -85,9 +86,7 @@ import java.io.DataOutputStream import java.io.File import java.io.FileInputStream import java.io.FileOutputStream -import java.text.Normalizer import java.util.Collections -import java.util.regex.Pattern import kotlin.math.max import kotlin.math.min @@ -953,11 +952,6 @@ class CardBrowserViewModel( private suspend fun shouldIgnoreAccents() = withCol { config.getBool(ConfigKey.Bool.IGNORE_ACCENTS_IN_SEARCH) } - private fun String.normalizeForSearch(): String { - val normalized = Normalizer.normalize(this, Normalizer.Form.NFD) - return Pattern.compile("\\p{InCombiningDiacriticalMarks}+").matcher(normalized).replaceAll("") - } - /** * @see com.ichi2.anki.searchForRows */ diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/String.kt b/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/String.kt new file mode 100644 index 000000000000..95141abb79e2 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/String.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Ashish Yadav + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +package com.ichi2.anki.utils.ext + +import java.text.Normalizer +import java.util.regex.Pattern + +private val DIACRITICAL_MARKS_PATTERN = Pattern.compile("\\p{InCombiningDiacriticalMarks}+") + +/** + * Normalizes the given string by removing diacritical marks (accents) to enable accent-insensitive searches. + * + * This method uses Unicode normalization in **NFD (Normalization Form Decomposition)** mode, which separates + * base characters from their diacritical marks. Then, it removes all combining diacritical marks using a regex. + * + * Example usage: + * ``` + * val input = "café naïve résumé" + * val normalized = input.normalizeForSearch() + * println(normalized) // Output: "cafe naive resume" + * ``` + * + * @receiver The input string that may contain accented characters. + * @return A new string with accents removed. + */ +fun String.normalizeForSearch(): String { + val normalized = Normalizer.normalize(this, Normalizer.Form.NFD) + return DIACRITICAL_MARKS_PATTERN.matcher(normalized).replaceAll("") +} diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/analytics/PreferencesAnalyticsTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/PreferencesAnalyticsTest.kt index 48b9842f310a..e1c3c004208f 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/analytics/PreferencesAnalyticsTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/PreferencesAnalyticsTest.kt @@ -89,7 +89,7 @@ class PreferencesAnalyticsTest : RobolectricTest() { R.string.hide_hard_and_easy_key, // hideHardAndEasy R.string.reviewer_frame_style_key, // reviewerFrameStyle R.string.hide_system_bars_key, // hideSystemBars - R.string.ignore_display_cutout_key, // ignoreDisplayCutout + R.string.ignore_display_cutout_key, // ignoreDisplayCutout, ).toStringResourceSet() @Test diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt index 86d3008e71f9..5dde1c6e5481 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt @@ -59,6 +59,7 @@ import com.ichi2.anki.model.SortType.SORT_FIELD import com.ichi2.anki.servicelayer.NoteService import com.ichi2.anki.setFlagFilterSync import com.ichi2.anki.utils.ext.ifNotZero +import com.ichi2.anki.utils.ext.normalizeForSearch import com.ichi2.libanki.CardId import com.ichi2.libanki.DeckId import com.ichi2.libanki.Note @@ -81,8 +82,11 @@ import org.hamcrest.Matchers.hasSize import org.hamcrest.Matchers.lessThan import org.hamcrest.Matchers.not import org.hamcrest.Matchers.nullValue +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.jupiter.api.assertInstanceOf +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource import org.junit.runner.RunWith import timber.log.Timber import java.io.File @@ -552,6 +556,21 @@ class CardBrowserViewModelTest : JvmTest() { } } + @ParameterizedTest + @CsvSource( + "café Ábaco naïve résumé, cafe Abaco naive resume", + "élégant déjà vu, elegant deja vu", + "hello world, hello world", + "'', ''", + "1234!@# café, 1234!@# cafe", + ) + fun `test normalizeForSearch`( + input: String, + expected: String, + ) { + assertEquals(expected, input.normalizeForSearch()) + } + @Test fun `suspend - notes - no selection`() = runViewModelNotesTest(notes = 2) {