|
1 | 1 | package dev.dettmer.simplenotes.ui.editor |
2 | 2 |
|
| 3 | +import dev.dettmer.simplenotes.models.ChecklistSortOption |
3 | 4 | import org.junit.Assert.* |
4 | 5 | import org.junit.Test |
5 | 6 |
|
@@ -174,4 +175,204 @@ class ChecklistSortingTest { |
174 | 175 | assertEquals(1, sorted[1].order) |
175 | 176 | assertEquals(2, sorted[2].order) |
176 | 177 | } |
| 178 | + |
| 179 | + // ═══════════════════════════════════════════════════════════════════════ |
| 180 | + // 🆕 v1.8.1 (IMPL_15): Tests für Add-Item Insert-Position |
| 181 | + // ═══════════════════════════════════════════════════════════════════════ |
| 182 | + |
| 183 | + /** |
| 184 | + * Simulates calculateInsertIndexForNewItem() from NoteEditorViewModel. |
| 185 | + * Tests the insert position logic for new unchecked items. |
| 186 | + */ |
| 187 | + private fun calculateInsertIndexForNewItem( |
| 188 | + items: List<ChecklistItemState>, |
| 189 | + sortOption: ChecklistSortOption |
| 190 | + ): Int { |
| 191 | + return when (sortOption) { |
| 192 | + ChecklistSortOption.MANUAL, |
| 193 | + ChecklistSortOption.UNCHECKED_FIRST -> { |
| 194 | + val firstCheckedIndex = items.indexOfFirst { it.isChecked } |
| 195 | + if (firstCheckedIndex >= 0) firstCheckedIndex else items.size |
| 196 | + } |
| 197 | + else -> items.size |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + /** |
| 202 | + * Simulates the full addChecklistItemAtEnd() logic: |
| 203 | + * 1. Calculate insert index |
| 204 | + * 2. Insert new item |
| 205 | + * 3. Reassign order values |
| 206 | + */ |
| 207 | + private fun simulateAddItemAtEnd( |
| 208 | + items: List<ChecklistItemState>, |
| 209 | + sortOption: ChecklistSortOption |
| 210 | + ): List<ChecklistItemState> { |
| 211 | + val newItem = ChecklistItemState(id = "new", text = "", isChecked = false, order = 0) |
| 212 | + val insertIndex = calculateInsertIndexForNewItem(items, sortOption) |
| 213 | + val newList = items.toMutableList() |
| 214 | + newList.add(insertIndex, newItem) |
| 215 | + return newList.mapIndexed { i, item -> item.copy(order = i) } |
| 216 | + } |
| 217 | + |
| 218 | + @Test |
| 219 | + fun `IMPL_15 - add item at end inserts before separator in MANUAL mode`() { |
| 220 | + // Ausgangslage: 2 unchecked, 1 checked (sortiert) |
| 221 | + val items = listOf( |
| 222 | + item("a", checked = false, order = 0), |
| 223 | + item("b", checked = false, order = 1), |
| 224 | + item("c", checked = true, order = 2) |
| 225 | + ) |
| 226 | + |
| 227 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.MANUAL) |
| 228 | + |
| 229 | + // Neues Item muss an Index 2 stehen (vor dem checked Item) |
| 230 | + assertEquals(4, result.size) |
| 231 | + assertEquals("a", result[0].id) |
| 232 | + assertEquals("b", result[1].id) |
| 233 | + assertEquals("new", result[2].id) // ← Neues Item VOR Separator |
| 234 | + assertFalse(result[2].isChecked) |
| 235 | + assertEquals("c", result[3].id) // ← Checked Item bleibt UNTER Separator |
| 236 | + assertTrue(result[3].isChecked) |
| 237 | + } |
| 238 | + |
| 239 | + @Test |
| 240 | + fun `IMPL_15 - add item at end inserts before separator in UNCHECKED_FIRST mode`() { |
| 241 | + val items = listOf( |
| 242 | + item("a", checked = false, order = 0), |
| 243 | + item("b", checked = true, order = 1), |
| 244 | + item("c", checked = true, order = 2) |
| 245 | + ) |
| 246 | + |
| 247 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.UNCHECKED_FIRST) |
| 248 | + |
| 249 | + assertEquals(4, result.size) |
| 250 | + assertEquals("a", result[0].id) |
| 251 | + assertEquals("new", result[1].id) // ← Neues Item direkt nach letztem unchecked |
| 252 | + assertFalse(result[1].isChecked) |
| 253 | + assertEquals("b", result[2].id) |
| 254 | + assertEquals("c", result[3].id) |
| 255 | + } |
| 256 | + |
| 257 | + @Test |
| 258 | + fun `IMPL_15 - add item at end appends at end in CHECKED_FIRST mode`() { |
| 259 | + val items = listOf( |
| 260 | + item("a", checked = true, order = 0), |
| 261 | + item("b", checked = false, order = 1) |
| 262 | + ) |
| 263 | + |
| 264 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.CHECKED_FIRST) |
| 265 | + |
| 266 | + assertEquals(3, result.size) |
| 267 | + assertEquals("a", result[0].id) |
| 268 | + assertEquals("b", result[1].id) |
| 269 | + assertEquals("new", result[2].id) // ← Am Ende (kein Separator) |
| 270 | + } |
| 271 | + |
| 272 | + @Test |
| 273 | + fun `IMPL_15 - add item at end appends at end in ALPHABETICAL_ASC mode`() { |
| 274 | + val items = listOf( |
| 275 | + item("a", checked = false, order = 0), |
| 276 | + item("b", checked = true, order = 1) |
| 277 | + ) |
| 278 | + |
| 279 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.ALPHABETICAL_ASC) |
| 280 | + |
| 281 | + assertEquals(3, result.size) |
| 282 | + assertEquals("new", result[2].id) // ← Am Ende |
| 283 | + } |
| 284 | + |
| 285 | + @Test |
| 286 | + fun `IMPL_15 - add item at end appends at end in ALPHABETICAL_DESC mode`() { |
| 287 | + val items = listOf( |
| 288 | + item("a", checked = true, order = 0), |
| 289 | + item("b", checked = false, order = 1) |
| 290 | + ) |
| 291 | + |
| 292 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.ALPHABETICAL_DESC) |
| 293 | + |
| 294 | + assertEquals(3, result.size) |
| 295 | + assertEquals("new", result[2].id) // ← Am Ende |
| 296 | + } |
| 297 | + |
| 298 | + @Test |
| 299 | + fun `IMPL_15 - add item with no checked items appends at end`() { |
| 300 | + val items = listOf( |
| 301 | + item("a", checked = false, order = 0), |
| 302 | + item("b", checked = false, order = 1) |
| 303 | + ) |
| 304 | + |
| 305 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.MANUAL) |
| 306 | + |
| 307 | + assertEquals(3, result.size) |
| 308 | + assertEquals("new", result[2].id) // Kein checked Item → ans Ende |
| 309 | + } |
| 310 | + |
| 311 | + @Test |
| 312 | + fun `IMPL_15 - add item with all checked items inserts at position 0`() { |
| 313 | + val items = listOf( |
| 314 | + item("a", checked = true, order = 0), |
| 315 | + item("b", checked = true, order = 1) |
| 316 | + ) |
| 317 | + |
| 318 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.MANUAL) |
| 319 | + |
| 320 | + assertEquals(3, result.size) |
| 321 | + assertEquals("new", result[0].id) // ← Ganz oben (vor allen checked Items) |
| 322 | + assertFalse(result[0].isChecked) |
| 323 | + assertEquals("a", result[1].id) |
| 324 | + assertEquals("b", result[2].id) |
| 325 | + } |
| 326 | + |
| 327 | + @Test |
| 328 | + fun `IMPL_15 - add item to empty list in MANUAL mode`() { |
| 329 | + val items = emptyList<ChecklistItemState>() |
| 330 | + |
| 331 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.MANUAL) |
| 332 | + |
| 333 | + assertEquals(1, result.size) |
| 334 | + assertEquals("new", result[0].id) |
| 335 | + assertEquals(0, result[0].order) |
| 336 | + } |
| 337 | + |
| 338 | + @Test |
| 339 | + fun `IMPL_15 - order values are sequential after add item`() { |
| 340 | + val items = listOf( |
| 341 | + item("a", checked = false, order = 0), |
| 342 | + item("b", checked = false, order = 1), |
| 343 | + item("c", checked = true, order = 2) |
| 344 | + ) |
| 345 | + |
| 346 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.MANUAL) |
| 347 | + |
| 348 | + result.forEachIndexed { index, item -> |
| 349 | + assertEquals("Order at index $index should be $index", index, item.order) |
| 350 | + } |
| 351 | + } |
| 352 | + |
| 353 | + @Test |
| 354 | + fun `IMPL_15 - existing items do not change position after add item`() { |
| 355 | + // Kernforderung: Kein Item darf sich verschieben |
| 356 | + val items = listOf( |
| 357 | + item("cashews", checked = false, order = 0), |
| 358 | + item("noodles", checked = false, order = 1), |
| 359 | + item("coffee", checked = true, order = 2) |
| 360 | + ) |
| 361 | + |
| 362 | + val result = simulateAddItemAtEnd(items, ChecklistSortOption.MANUAL) |
| 363 | + |
| 364 | + // Relative Reihenfolge der bestehenden Items prüfen |
| 365 | + val existingIds = result.filter { it.id != "new" }.map { it.id } |
| 366 | + assertEquals(listOf("cashews", "noodles", "coffee"), existingIds) |
| 367 | + |
| 368 | + // Cashews und Noodles müssen VOR dem neuen Item sein |
| 369 | + val cashewsIdx = result.indexOfFirst { it.id == "cashews" } |
| 370 | + val noodlesIdx = result.indexOfFirst { it.id == "noodles" } |
| 371 | + val newIdx = result.indexOfFirst { it.id == "new" } |
| 372 | + val coffeeIdx = result.indexOfFirst { it.id == "coffee" } |
| 373 | + |
| 374 | + assertTrue("Cashews before new", cashewsIdx < newIdx) |
| 375 | + assertTrue("Noodles before new", noodlesIdx < newIdx) |
| 376 | + assertTrue("New before Coffee", newIdx < coffeeIdx) |
| 377 | + } |
177 | 378 | } |
0 commit comments