From ce8e3f2fa0081afc23f99297e8737f94eac3fc01 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Sun, 11 May 2025 00:44:18 +0530 Subject: [PATCH] Allow search to show subdecks if they match the searched term --- .../java/com/ichi2/libanki/sched/DeckNode.kt | 6 +- .../java/com/ichi2/libanki/DeckNodeTest.kt | 110 ++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 AnkiDroid/src/test/java/com/ichi2/libanki/DeckNodeTest.kt diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckNode.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckNode.kt index 6c4cd2c7d378..4c6aadfa725d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckNode.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckNode.kt @@ -113,9 +113,13 @@ data class DeckNode( return } - if (collapsed) { + // When searching, ignore collapsed state and always search children + val searching = filter != null + if (collapsed && !searching) { return } + // TODO: Fix collapse/uncollapse icons showing during search even when they are not usable. + // See issue #18379 for more details. if (node.level > 0) { list.append(this) diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/DeckNodeTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/DeckNodeTest.kt new file mode 100644 index 000000000000..df79f08443d5 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/DeckNodeTest.kt @@ -0,0 +1,110 @@ +/**************************************************************************************** + * * + * Copyright (c) 2025 Shridhar Goel * + * * + * 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.libanki + +import anki.decks.deckTreeNode +import com.ichi2.libanki.sched.DeckNode +import org.junit.Assert.assertEquals +import org.junit.Test + +class DeckNodeTest { + private fun makeNode( + name: String, + deckId: Long, + level: Int, + collapsed: Boolean = false, + children: List = emptyList(), + ): DeckNode { + val treeNode = + deckTreeNode { + this.name = name + this.deckId = deckId + this.level = level + this.collapsed = collapsed + children.forEach { this.children.add(it.node) } + this.reviewCount = 0 + this.newCount = 0 + this.learnCount = 0 + this.filtered = false + } + return DeckNode(treeNode, name) + } + + @Test + fun `search finds subdeck even if parent collapsed`() { + // Science::Math::Algebra::Group + val group = makeNode("Group", 4, 4) + val algebra = makeNode("Algebra", 3, 3, children = listOf(group)) + val math = makeNode("Math", 2, 2, collapsed = true, children = listOf(algebra)) + val science = makeNode("Science", 1, 1, children = listOf(math)) + + val results1 = science.filterAndFlatten("group") + assertEquals(listOf("Science", "Math", "Algebra", "Group"), results1.map { it.lastDeckNameComponent }) + + val results2 = science.filterAndFlatten("math") + assertEquals(listOf("Science", "Math"), results2.map { it.lastDeckNameComponent }) + } + + @Test + fun `collapsed parent hides children when not searching`() { + val group = makeNode("Group", 4, 4) + val algebra = makeNode("Algebra", 3, 3, children = listOf(group)) + val math = makeNode("Math", 2, 2, collapsed = true, children = listOf(algebra)) + val science = makeNode("Science", 1, 1, children = listOf(math)) + + val results = math.filterAndFlatten(null) + assertEquals(1, results.size) + assertEquals("Math", results[0].lastDeckNameComponent) + } + + @Test + fun `search for non-matching term returns no results`() { + // Science::Math::Algebra::Group + val group = makeNode("Group", 4, 4) + val algebra = makeNode("Algebra", 3, 3, children = listOf(group)) + val math = makeNode("Math", 2, 2, collapsed = true, children = listOf(algebra)) + val science = makeNode("Science", 1, 1, children = listOf(math)) + + val results = science.filterAndFlatten("complex") + assertEquals(emptyList(), results.map { it.lastDeckNameComponent }) + } + + @Test + fun `search for algebra shows all decks in the path`() { + // Science::Math::Algebra::Group + val group = makeNode("Group", 4, 4) + val algebra = makeNode("Algebra", 3, 3, children = listOf(group)) + val math = makeNode("Math", 2, 2, collapsed = false, children = listOf(algebra)) + val science = makeNode("Science", 1, 1, children = listOf(math)) + + val results = science.filterAndFlatten("algebra") + assertEquals(listOf("Science", "Math", "Algebra", "Group"), results.map { it.lastDeckNameComponent }) + } + + @Test + fun `search for non-existent path pattern returns no results`() { + // Science::Math::Algebra::Group + val group = makeNode("Group", 4, 4) + val algebra = makeNode("Algebra", 3, 3, children = listOf(group)) + val math = makeNode("Math", 2, 2, collapsed = false, children = listOf(algebra)) + val science = makeNode("Science", 1, 1, children = listOf(math)) + + val results = science.filterAndFlatten("th::alg") + assertEquals(emptyList(), results.map { it.lastDeckNameComponent }) + } +}