1+ package lsp
2+
3+ import completions.lsp.util.completions.FuzzyCompletionRanking.rankCompletions
4+ import lsp.RankingTestDSL.rankingTest
5+ import org.eclipse.lsp4j.CompletionItem
6+ import org.junit.jupiter.api.Assertions.assertIterableEquals
7+ import org.junit.jupiter.api.Test
8+
9+ class FuzzyCompletionRankingTest {
10+
11+ @Test
12+ fun `empty query ranks only by sortText` () = rankingTest(" " ) {
13+ val a = item(" zeta" , sortText = " 3" )
14+ val b = item(" alpha" , sortText = " 1" )
15+ val c = item(" gamma" , sortText = " 2" )
16+ expectOrder(b, c, a)
17+ }
18+
19+ @Test
20+ fun `Kotlin common API names should be properly ranked` () = rankingTest(" pr" ) {
21+ val println = item(" println" , sortText = " 2" )
22+ val print = item(" print" , sortText = " 1" )
23+ val property = item(" property" , sortText = " 4" )
24+ val map = item(" map" , sortText = " 3" )
25+ expectOrder(print, println, property, map)
26+ }
27+
28+ @Test
29+ fun `completions are ranked by relevance and length (shortest first)` () = rankingTest(" toIn" ) {
30+ val c1 = item(" toInt" , sortText = " 3" )
31+ val c2 = item(" toUInt" , sortText = " 1" )
32+ val c3 = item(" toInterval" , sortText = " 2" )
33+ expectOrder(c1, c3, c2)
34+ }
35+
36+ @Test
37+ fun `tie break by sortText when scores equal` () = rankingTest(" pr" ) {
38+ val a = item(" prX" , sortText = " 1" )
39+ val b = item(" prY" , sortText = " 2" )
40+ expectOrder(a, b)
41+ }
42+
43+ @Test
44+ fun `exact and consecutive matches outrank sparse matches` () = rankingTest(" pr" ) {
45+ val a = item(" print" , sortText = " 2" ) // exact
46+ val b = item(" p...r.." , sortText = " 1" ) // non-consecutive
47+ val c = item(" pxxx" , sortText = " 3" ) // sparse/non-full
48+ expectOrder(a, b, c)
49+ }
50+
51+ @Test
52+ fun `case insensitive matching, tie-break with sortText` () = rankingTest(" PrI" ) {
53+ val a = item(" pRiNtLn" , sortText = " 2" )
54+ val b = item(" println" , sortText = " 1" )
55+ expectOrder(b, a)
56+ }
57+
58+ @Test
59+ fun `consecutive matches beat non-consecutive` () = rankingTest(" io" ) {
60+ val consecutive = item(" ioScope" , sortText = " 2" )
61+ val sparse = item(" iXoScope" , sortText = " 1" )
62+ expectOrder(consecutive, sparse)
63+ }
64+ }
65+
66+ private object RankingTestDSL {
67+ data class RankingCase (private val query : String ) {
68+ val items = mutableListOf<CompletionItem >()
69+ val expected = mutableListOf<CompletionItem >()
70+
71+ fun item (
72+ label : String ,
73+ sortText : String ,
74+ dataJson : String? = null,
75+ ): CompletionItem = CompletionItem (label).apply {
76+ this .sortText = sortText
77+ if (dataJson != null ) this .data = dataJson
78+ items + = this
79+ }
80+
81+ fun expectOrder (vararg items : CompletionItem ) {
82+ expected.clear()
83+ expected + = items
84+ }
85+ }
86+
87+ fun rankingTest (query : String , build : RankingCase .() -> Unit ) {
88+ val case = RankingCase (query).apply (build)
89+ val ranked = case.items.rankCompletions(query)
90+ assertIterableEquals(case.expected, ranked, " Expected(query=$query ): ${case.expected} but got: $ranked " )
91+ }
92+ }
0 commit comments