1+ package lsp
2+
3+ import completions.lsp.util.completions.FuzzyCompletionRanking.rankCompletions
4+ import org.eclipse.lsp4j.CompletionItem
5+ import org.junit.jupiter.api.Assertions.assertIterableEquals
6+ import org.junit.jupiter.api.Test
7+
8+ class FuzzyCompletionRankingTest {
9+
10+ @Test
11+ fun `empty query ranks only by sortText` () {
12+ val query = " "
13+
14+ val a = completionItem(" zeta" , sortText = " 3" )
15+ val b = completionItem(" alpha" , sortText = " 1" )
16+ val c = completionItem(" gamma" , sortText = " 2" )
17+ val ranked = listOf (a, b, c).rankCompletions(query)
18+
19+ assertIterableEquals(listOf (b, c, a), ranked)
20+ }
21+
22+ @Test
23+ fun `Kotlin common API names should be properly ranked` () {
24+ val query = " pr"
25+
26+ val println = completionItem(" println" , sortText = " 2" )
27+ val print = completionItem(" print" , sortText = " 1" )
28+ val property = completionItem(" property" , sortText = " 4" )
29+ val map = completionItem(" map" , sortText = " 3" )
30+
31+ val ranked = listOf (map, println, property, print).rankCompletions(query)
32+
33+ assertIterableEquals(listOf (print, println, property, map), ranked)
34+ }
35+
36+ @Test
37+ fun `completions are ranked properly` () {
38+ val c1 = completionItem(" toInt" , sortText = " 3" )
39+ val c2 = completionItem(" toUInt" , sortText = " 1" )
40+ val c3 = completionItem(" toInterval" , sortText = " 2" )
41+
42+ val ranked = listOf (c2, c3, c1).rankCompletions(" toIn" )
43+ assertIterableEquals(listOf (c1, c3, c2), ranked)
44+ }
45+
46+ @Test
47+ fun `tie-break by sortText when fuzzy scores equal` () {
48+ val query = " pr"
49+
50+ val a = completionItem(" prX" , sortText = " 1" )
51+ val b = completionItem(" prY" , sortText = " 2" )
52+ val ranked = listOf (b, a).rankCompletions(query)
53+
54+ assertIterableEquals(listOf (a, b), ranked)
55+ }
56+
57+ @Test
58+ fun `exact and consecutive matches outrank sparse matches` () {
59+ val query = " pr"
60+
61+ val a = completionItem(" print" , sortText = " 2" ) // exact
62+ val b = completionItem(" p...r.." , sortText = " 1" ) // consecutive
63+ val c = completionItem(" pxxx" , sortText = " 3" ) // sparse
64+ val ranked = listOf (a, b, c).rankCompletions(query)
65+
66+ assertIterableEquals(listOf (a, b, c), ranked)
67+ }
68+
69+ @Test
70+ fun `case-insensitive matching, tie-break with sortText` () {
71+ val query = " PrI"
72+
73+ val a = completionItem(" pRiNtLn" , sortText = " 2" )
74+ val b = completionItem(" println" , sortText = " 1" )
75+ val ranked = listOf (a, b).rankCompletions(query)
76+
77+ assertIterableEquals(listOf (b, a), ranked)
78+ }
79+
80+ @Test
81+ fun `non-matching candidates are ranked after matching ones` () {
82+ val query = " map"
83+
84+ val match1 = completionItem(" map" , sortText = " 2" )
85+ val match2 = completionItem(" maybeApply" , sortText = " 3" )
86+ val nonMatch = completionItem(" println" , sortText = " 1" )
87+ val ranked = listOf (nonMatch, match2, match1).rankCompletions(query)
88+
89+ assertIterableEquals(listOf (match1, match2, nonMatch), ranked)
90+ }
91+
92+ @Test
93+ fun `consecutive matches beat non-consecutive` () {
94+ val query = " io"
95+
96+ val consecutive = completionItem(" ioScope" , sortText = " 2" )
97+ val sparse = completionItem(" iXoScope" , sortText = " 1" )
98+ val ranked = listOf (sparse, consecutive).rankCompletions(query)
99+
100+ assertIterableEquals(listOf (consecutive, sparse), ranked)
101+ }
102+
103+
104+
105+ private fun completionItem (
106+ label : String ,
107+ sortText : String ,
108+ filterText : String? = null,
109+ dataJson : String? = null,
110+ ): CompletionItem = CompletionItem (label).apply {
111+ this .sortText = sortText
112+ if (filterText != null ) this .filterText = filterText
113+ if (dataJson != null ) this .data = dataJson
114+ }
115+ }
0 commit comments