Skip to content

Commit 829f6c6

Browse files
c-sus-suslukstbit
authored andcommitted
Add validation for empty note type name
1 parent d682e4c commit 829f6c6

4 files changed

Lines changed: 147 additions & 7 deletions

File tree

AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddNewNotesType.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ class AddNewNotesType(
7878
.apply {
7979
customView(binding.root, paddingStart = 32, paddingEnd = 32, paddingTop = 64, paddingBottom = 64)
8080
positiveButton(R.string.dialog_ok) { _ ->
81-
val newName = binding.notetypeNewName.text.toString()
81+
val newName =
82+
binding.notetypeNewName.text
83+
.toString()
84+
.trim()
85+
if (newName.isEmpty()) return@positiveButton
8286
val selectedPosition = binding.notetypeNewType.selectedItemPosition
8387
if (selectedPosition == AdapterView.INVALID_POSITION) return@positiveButton
8488
val selectedOption = allOptions[selectedPosition]
@@ -100,7 +104,7 @@ class AddNewNotesType(
100104
val addPrefixStr = context.resources.getString(R.string.model_browser_add_add)
101105
val clonePrefixStr = context.resources.getString(R.string.model_browser_add_clone)
102106
binding.notetypeNewName.addTextChangedListener { editableText ->
103-
val currentName = editableText?.toString() ?: ""
107+
val currentName = editableText?.toString()?.trim() ?: ""
104108
positiveButton.isEnabled =
105109
currentName.isNotEmpty() &&
106110
!currentNames.contains(currentName)

AnkiDroid/src/main/java/com/ichi2/anki/notetype/ManageNotetypes.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import androidx.activity.result.ActivityResult
2525
import androidx.activity.result.contract.ActivityResultContracts
2626
import androidx.activity.viewModels
2727
import androidx.annotation.StringRes
28+
import androidx.annotation.VisibleForTesting
2829
import androidx.appcompat.app.AlertDialog
2930
import androidx.appcompat.widget.SearchView
3031
import androidx.appcompat.widget.Toolbar
@@ -217,7 +218,8 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
217218
return true
218219
}
219220

220-
private fun renameNotetype(state: NoteTypeItemState) {
221+
@VisibleForTesting
222+
internal fun renameNotetype(state: NoteTypeItemState) {
221223
launchCatchingTask {
222224
val allNotetypes = viewModel.state.value.noteTypes
223225
val dialog =
@@ -226,7 +228,13 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
226228
.show {
227229
title(R.string.rename_model)
228230
positiveButton(R.string.rename) {
229-
val userInput = (it as AlertDialog).getInputField().text.toString()
231+
val userInput =
232+
(it as AlertDialog)
233+
.getInputField()
234+
.text
235+
.toString()
236+
.trim()
237+
if (userInput.isEmpty()) return@positiveButton
230238
viewModel.rename(state.id, userInput)
231239
}
232240
negativeButton(R.string.dialog_cancel)
@@ -236,17 +244,19 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
236244
waitForPositiveButton = false,
237245
displayKeyboard = true,
238246
callback = { dialog, text ->
247+
val currentName = text.toString().trim()
239248
val isNotADuplicate =
240-
!allNotetypes.map { it.name }.contains(text.toString())
241-
dialog.positiveButton.isEnabled = text.isNotEmpty() && isNotADuplicate
249+
!allNotetypes.map { it.name }.contains(currentName)
250+
dialog.positiveButton.isEnabled = currentName.isNotEmpty() && isNotADuplicate
242251
},
243252
)
244253
// start with the button disabled as dialog shows the initial name
245254
dialog.positiveButton.isEnabled = false
246255
}
247256
}
248257

249-
private fun deleteNotetype(state: NoteTypeItemState) {
258+
@VisibleForTesting
259+
internal fun deleteNotetype(state: NoteTypeItemState) {
250260
launchCatchingTask {
251261
@StringRes val messageResourceId: Int? =
252262
if (userAcceptsSchemaChange()) {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2026 Chaitanya Medidar <2023.chaitanya.medidar@ves.ac.in>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
package com.ichi2.anki.notetype
17+
18+
import android.widget.EditText
19+
import androidx.appcompat.app.AlertDialog
20+
import androidx.lifecycle.lifecycleScope
21+
import androidx.test.ext.junit.runners.AndroidJUnit4
22+
import com.ichi2.anki.R
23+
import com.ichi2.anki.RobolectricTest
24+
import kotlinx.coroutines.launch
25+
import org.hamcrest.MatcherAssert.assertThat
26+
import org.hamcrest.Matchers.equalTo
27+
import org.junit.Test
28+
import org.junit.runner.RunWith
29+
import org.robolectric.shadows.ShadowDialog
30+
import org.robolectric.shadows.ShadowLooper
31+
32+
@RunWith(AndroidJUnit4::class)
33+
class AddNewNotesTypeTest : RobolectricTest() {
34+
@Test
35+
fun `add note type - whitespace only name keeps OK button disabled`() =
36+
runTest {
37+
val activity = startRegularActivity<ManageNotetypes>()
38+
val addNewNotesType = AddNewNotesType(activity)
39+
activity.lifecycleScope.launch {
40+
addNewNotesType.showAddNewNotetypeDialog()
41+
}
42+
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
43+
44+
val dialog = ShadowDialog.getLatestDialog() as AlertDialog
45+
val nameInput = dialog.findViewById<EditText>(R.id.notetype_new_name)!!
46+
val positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
47+
48+
// whitespace-only should disable the OK button
49+
nameInput.setText(" ")
50+
assertThat("OK button disabled for whitespace-only name", positiveButton.isEnabled, equalTo(false))
51+
52+
// valid name should enable the button
53+
nameInput.setText("My New Note Type")
54+
assertThat("OK button enabled for a valid name", positiveButton.isEnabled, equalTo(true))
55+
56+
// empty string should disable it again
57+
nameInput.setText("")
58+
assertThat("OK button disabled for empty name", positiveButton.isEnabled, equalTo(false))
59+
}
60+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2026 Chaitanya Medidar <2023.chaitanya.medidar@ves.ac.in>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
package com.ichi2.anki.notetype
17+
18+
import android.widget.EditText
19+
import androidx.appcompat.app.AlertDialog
20+
import androidx.test.ext.junit.runners.AndroidJUnit4
21+
import com.ichi2.anki.R
22+
import com.ichi2.anki.RobolectricTest
23+
import org.hamcrest.MatcherAssert.assertThat
24+
import org.hamcrest.Matchers.equalTo
25+
import org.junit.Test
26+
import org.junit.runner.RunWith
27+
import org.robolectric.shadows.ShadowDialog
28+
import org.robolectric.shadows.ShadowLooper
29+
30+
@RunWith(AndroidJUnit4::class)
31+
class ManageNotetypesTest : RobolectricTest() {
32+
@Test
33+
fun `rename note type - whitespace only name keeps Rename button disabled`() =
34+
runTest {
35+
ensureCollectionLoadIsSynchronous()
36+
addStandardNoteType(TEST_NOTE_TYPE_NAME, arrayOf("front", "back"), "", "")
37+
38+
val activity = startRegularActivity<ManageNotetypes>()
39+
val firstNoteType =
40+
activity.viewModel.state.value.noteTypes
41+
.first { it.name == TEST_NOTE_TYPE_NAME }
42+
activity.renameNotetype(firstNoteType)
43+
44+
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
45+
46+
val dialog = ShadowDialog.getLatestDialog() as AlertDialog
47+
val nameInput = dialog.findViewById<EditText>(R.id.dialog_text_input)!!
48+
val positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
49+
50+
// whitespace-only should disable the Rename button
51+
nameInput.setText(" ")
52+
assertThat("Rename button disabled for whitespace-only name", positiveButton.isEnabled, equalTo(false))
53+
54+
// a valid new name should enable the button
55+
nameInput.setText("Renamed Note Type")
56+
assertThat("Rename button enabled for a valid name", positiveButton.isEnabled, equalTo(true))
57+
58+
// empty string should disable it again
59+
nameInput.setText("")
60+
assertThat("Rename button disabled for empty name", positiveButton.isEnabled, equalTo(false))
61+
}
62+
63+
companion object {
64+
private const val TEST_NOTE_TYPE_NAME = "BasicTestNoteType1"
65+
}
66+
}

0 commit comments

Comments
 (0)