Skip to content

Commit 83a89f1

Browse files
authored
fix(hierarchical): deselect item (#342)
1 parent e12cb10 commit 83a89f1

File tree

4 files changed

+54
-6
lines changed

4 files changed

+54
-6
lines changed

instantsearch/src/commonMain/kotlin/com/algolia/instantsearch/hierarchical/HierarchicalPath.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ package com.algolia.instantsearch.hierarchical
22

33
import com.algolia.search.model.Attribute
44

5+
/**
6+
* Hierarchical path in a tree.
7+
*
8+
* Example: [("lvl0", "A"), ("lvl1", "A > B"), ("lvl2", "A > B > C")]
9+
*/
510
public typealias HierarchicalPath = List<Pair<Attribute, String>>

instantsearch/src/commonMain/kotlin/com/algolia/instantsearch/hierarchical/HierarchicalViewModel.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,25 @@ public open class HierarchicalViewModel(
3636
*/
3737
override fun computeSelections(key: String) {
3838
val selections = key.toSelectionList()
39-
val hierarchicalPath = hierarchicalAttributes.mapIndexed { index, item ->
40-
selections.getOrNull(index)?.let { item to it }
41-
}.filterNotNull()
39+
val updatedHierarchicalPath = hierarchicalAttributes
40+
.mapIndexed { index, item -> selections.getOrNull(index)?.let { item to it } }
41+
.filterNotNull()
42+
.updateIfDeselect(hierarchicalPath.value)
4243

43-
this.hierarchicalPath.value = hierarchicalPath
44-
eventHierarchicalPath.send(hierarchicalPath)
44+
hierarchicalPath.value = updatedHierarchicalPath
45+
eventHierarchicalPath.send(updatedHierarchicalPath)
4546
}
4647

4748
private fun String.toSelectionList(): List<String> = split(separator).fold(listOf()) { acc, s ->
4849
acc + if (acc.isEmpty()) s else acc.last() + separator + s
4950
}
51+
52+
/**
53+
* Remove the last item from the hierarchical path if it's a deselection operation.
54+
* Returning an empty list indicates that no item is selected.
55+
*/
56+
private fun HierarchicalPath.updateIfDeselect(current: HierarchicalPath): HierarchicalPath {
57+
if (this != current) return this // new path selected, nothing to do
58+
return dropLast(1)
59+
}
5060
}

instantsearch/src/commonMain/kotlin/com/algolia/instantsearch/hierarchical/internal/HierarchicalConnectionFilterState.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ internal data class HierarchicalConnectionFilterState(
2323

2424
private val updateFilterState: Callback<HierarchicalPath> = { selections ->
2525
filterState.notify {
26+
if (selections.isEmpty()) { // No item selected
27+
remove(viewModel.attribute)
28+
return@notify
29+
}
30+
2631
val last = selections.last()
2732
val filter = Filter.Facet(last.first, last.second)
2833
val path = selections.map { Filter.Facet(it.first, it.second) }

instantsearch/src/commonTest/kotlin/hierarchical/TestHierarchicalConnectFilterState.kt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import com.algolia.instantsearch.hierarchical.HierarchicalViewModel
99
import com.algolia.instantsearch.hierarchical.connectFilterState
1010
import com.algolia.search.model.Attribute
1111
import com.algolia.search.model.search.Facet
12+
import kotlin.test.Test
1213
import shouldBeNull
1314
import shouldEqual
14-
import kotlin.test.Test
1515

1616
class TestHierarchicalConnectFilterState {
1717

@@ -88,4 +88,32 @@ class TestHierarchicalConnectFilterState {
8888
}
8989
viewModel.selections.value shouldEqual listOf(facetBags.value)
9090
}
91+
92+
@Test
93+
fun onDeselectShouldUpdateFilterState() {
94+
val viewModel = HierarchicalViewModel(category, categories, separator, tree)
95+
val filterState = FilterState()
96+
val connection = viewModel.connectFilterState(filterState)
97+
connection.connect()
98+
99+
// Select from last level (lvl1)
100+
viewModel.computeSelections(facetShoesRunning.value)
101+
filterState.getHierarchicalFilters(category) shouldEqual HierarchicalFilter(
102+
attributes = viewModel.hierarchicalAttributes,
103+
filter = filterShoesRunning,
104+
path = listOf(filterShoes, filterShoesRunning)
105+
)
106+
107+
// Deselect the same item (lvl1) -> get up one level (lvl0)
108+
viewModel.computeSelections(facetShoesRunning.value)
109+
filterState.getHierarchicalFilters(category) shouldEqual HierarchicalFilter(
110+
attributes = viewModel.hierarchicalAttributes,
111+
filter = filterShoes,
112+
path = listOf(filterShoes)
113+
)
114+
115+
// Deselect the last item from (lvl0)
116+
viewModel.computeSelections(facetShoes.value)
117+
filterState.getHierarchicalFilters(category) shouldEqual null // corresponding filter should be removed
118+
}
91119
}

0 commit comments

Comments
 (0)