git.iko.soy/parseable-macos
on 2026.02.15.2233 help sign in
parseable-macos/ParseableViewerTests/SQLAutoCompleteTests.swift
281 lines 10 kB history raw
1import XCTest
2@testable import ParseableViewer
4final class SQLAutoCompleteTests: XCTestCase {
6 private let streams = ["access_logs", "error_logs", "metrics"]
7 private let fields = [
8 SchemaField(name: "p_timestamp", dataType: "Utf8"),
9 SchemaField(name: "level", dataType: "Utf8"),
10 SchemaField(name: "message", dataType: "Utf8"),
11 SchemaField(name: "host", dataType: "Utf8"),
12 SchemaField(name: "status_code", dataType: "Int64"),
13 ]
15 // MARK: - Context detection
17 func testContextAfterSelect() {
18 let ctx = SQLCompletionProvider.determineContext(text: "SELECT ", position: 7)
19 XCTAssertEqual(ctx, .columnRef)
20 }
22 func testContextAfterFrom() {
23 let ctx = SQLCompletionProvider.determineContext(text: "SELECT * FROM ", position: 14)
24 XCTAssertEqual(ctx, .tableRef)
25 }
27 func testContextAfterWhere() {
28 let ctx = SQLCompletionProvider.determineContext(text: "SELECT * FROM t WHERE ", position: 22)
29 XCTAssertEqual(ctx, .columnRef)
30 }
32 func testContextAfterAnd() {
33 let ctx = SQLCompletionProvider.determineContext(text: "SELECT * FROM t WHERE x = 1 AND ", position: 32)
34 XCTAssertEqual(ctx, .columnRef)
35 }
37 func testContextAfterJoin() {
38 let ctx = SQLCompletionProvider.determineContext(text: "SELECT * FROM a JOIN ", position: 21)
39 XCTAssertEqual(ctx, .tableRef)
40 }
42 func testContextAfterOrder() {
43 let ctx = SQLCompletionProvider.determineContext(text: "SELECT * FROM t ORDER ", position: 22)
44 XCTAssertEqual(ctx, .afterOrder)
45 }
47 func testContextAfterGroup() {
48 let ctx = SQLCompletionProvider.determineContext(text: "SELECT * FROM t GROUP ", position: 22)
49 XCTAssertEqual(ctx, .afterGroup)
50 }
52 func testContextAfterCommaInSelect() {
53 let ctx = SQLCompletionProvider.determineContext(text: "SELECT a, ", position: 10)
54 XCTAssertEqual(ctx, .columnRef)
55 }
57 func testContextAfterCommaInFrom() {
58 let ctx = SQLCompletionProvider.determineContext(text: "SELECT * FROM a, ", position: 17)
59 XCTAssertEqual(ctx, .tableRef)
60 }
62 func testContextGeneral() {
63 let ctx = SQLCompletionProvider.determineContext(text: "", position: 0)
64 XCTAssertEqual(ctx, .general)
65 }
67 // MARK: - Completions for keywords
69 func testKeywordCompletionSelect() {
70 let result = SQLCompletionProvider.completions(
71 for: "SEL", cursorPosition: 3, streamNames: streams, schemaFields: fields
72 )
73 XCTAssertTrue(result.items.contains(where: { $0.text == "SELECT" }))
74 XCTAssertEqual(result.prefix, "SEL")
75 XCTAssertEqual(result.range, NSRange(location: 0, length: 3))
76 }
78 func testKeywordCompletionCaseInsensitive() {
79 let result = SQLCompletionProvider.completions(
80 for: "sel", cursorPosition: 3, streamNames: streams, schemaFields: fields
81 )
82 XCTAssertTrue(result.items.contains(where: { $0.text == "SELECT" }))
83 }
85 func testNoCompletionOnExactMatch() {
86 let result = SQLCompletionProvider.completions(
87 for: "SELECT", cursorPosition: 6, streamNames: [], schemaFields: []
88 )
89 // When the only match is exact, no items are returned
90 XCTAssertTrue(result.items.isEmpty)
91 }
93 // MARK: - Table name completions
95 func testTableCompletionAfterFrom() {
96 let result = SQLCompletionProvider.completions(
97 for: "SELECT * FROM ac", cursorPosition: 16, streamNames: streams, schemaFields: fields
98 )
99 XCTAssertTrue(result.items.contains(where: { $0.text == "\"access_logs\"" && $0.kind == .table }))
100 XCTAssertFalse(result.items.contains(where: { $0.text == "\"metrics\"" }))
101 }
103 func testTableCompletionIncludesQuotes() {
104 let result = SQLCompletionProvider.completions(
105 for: "SELECT * FROM err", cursorPosition: 17, streamNames: streams, schemaFields: fields
106 )
107 let tableItem = result.items.first(where: { $0.kind == .table })
108 XCTAssertNotNil(tableItem)
109 XCTAssertEqual(tableItem?.text, "\"error_logs\"")
110 }
112 // MARK: - Column completions
114 func testColumnCompletionAfterWhere() {
115 let result = SQLCompletionProvider.completions(
116 for: "SELECT * FROM t WHERE lev", cursorPosition: 25,
117 streamNames: streams, schemaFields: fields
118 )
119 XCTAssertTrue(result.items.contains(where: { $0.text == "level" && $0.kind == .column }))
120 }
122 func testColumnCompletionIncludesDetail() {
123 let result = SQLCompletionProvider.completions(
124 for: "SELECT * FROM t WHERE sta", cursorPosition: 25,
125 streamNames: streams, schemaFields: fields
126 )
127 let colItem = result.items.first(where: { $0.kind == .column })
128 XCTAssertNotNil(colItem)
129 XCTAssertEqual(colItem?.detail, "Int64")
130 }
132 func testColumnCompletionAfterSelect() {
133 let result = SQLCompletionProvider.completions(
134 for: "SELECT mes", cursorPosition: 10,
135 streamNames: streams, schemaFields: fields
136 )
137 XCTAssertTrue(result.items.contains(where: { $0.text == "message" && $0.kind == .column }))
138 }
140 // MARK: - Function completions
142 func testFunctionCompletion() {
143 let result = SQLCompletionProvider.completions(
144 for: "SELECT COU", cursorPosition: 10,
145 streamNames: streams, schemaFields: fields
146 )
147 XCTAssertTrue(result.items.contains(where: { $0.text == "COUNT" && $0.kind == .function }))
148 }
150 // MARK: - Empty prefix returns nothing
152 func testEmptyPrefixNoCompletions() {
153 let result = SQLCompletionProvider.completions(
154 for: "SELECT ", cursorPosition: 7,
155 streamNames: streams, schemaFields: fields
156 )
157 XCTAssertTrue(result.items.isEmpty)
158 }
160 // MARK: - Prefix range
162 func testPrefixRangeInMiddle() {
163 let text = "SELECT * FROM acc WHERE"
164 let result = SQLCompletionProvider.completions(
165 for: text, cursorPosition: 17,
166 streamNames: streams, schemaFields: fields
167 )
168 // "acc" starts at position 14
169 XCTAssertEqual(result.range, NSRange(location: 14, length: 3))
170 XCTAssertEqual(result.prefix, "acc")
171 }
173 // MARK: - ORDER BY / GROUP BY
175 func testOrderByCompletion() {
176 let result = SQLCompletionProvider.completions(
177 for: "SELECT * FROM t ORDER B", cursorPosition: 23,
178 streamNames: streams, schemaFields: fields
179 )
180 XCTAssertTrue(result.items.contains(where: { $0.text == "BY" }))
181 }
183 func testGroupByCompletion() {
184 let result = SQLCompletionProvider.completions(
185 for: "SELECT * FROM t GROUP B", cursorPosition: 23,
186 streamNames: streams, schemaFields: fields
187 )
188 XCTAssertTrue(result.items.contains(where: { $0.text == "BY" }))
189 }
191 // MARK: - isWordCharacter
193 func testWordCharacters() {
194 XCTAssertTrue(SQLCompletionProvider.isWordCharacter(0x41)) // A
195 XCTAssertTrue(SQLCompletionProvider.isWordCharacter(0x5A)) // Z
196 XCTAssertTrue(SQLCompletionProvider.isWordCharacter(0x61)) // a
197 XCTAssertTrue(SQLCompletionProvider.isWordCharacter(0x7A)) // z
198 XCTAssertTrue(SQLCompletionProvider.isWordCharacter(0x30)) // 0
199 XCTAssertTrue(SQLCompletionProvider.isWordCharacter(0x39)) // 9
200 XCTAssertTrue(SQLCompletionProvider.isWordCharacter(0x5F)) // _
201 XCTAssertFalse(SQLCompletionProvider.isWordCharacter(0x20)) // space
202 XCTAssertFalse(SQLCompletionProvider.isWordCharacter(0x2C)) // comma
203 XCTAssertFalse(SQLCompletionProvider.isWordCharacter(0x28)) // (
204 }
206 // MARK: - Edge cases
208 func testCursorAtZero() {
209 let result = SQLCompletionProvider.completions(
210 for: "SELECT", cursorPosition: 0,
211 streamNames: streams, schemaFields: fields
212 )
213 XCTAssertTrue(result.items.isEmpty)
214 }
216 func testCursorBeyondEnd() {
217 let result = SQLCompletionProvider.completions(
218 for: "SEL", cursorPosition: 100,
219 streamNames: streams, schemaFields: fields
220 )
221 XCTAssertTrue(result.items.isEmpty)
222 }
224 func testEmptyText() {
225 let result = SQLCompletionProvider.completions(
226 for: "", cursorPosition: 0,
227 streamNames: streams, schemaFields: fields
228 )
229 XCTAssertTrue(result.items.isEmpty)
230 }
232 // MARK: - Mixed context completions
234 func testGeneralContextIncludesAllKinds() {
235 // Typing "m" at the start should match keywords (no keywords start with m),
236 // functions, tables ("metrics"), and columns ("message")
237 let result = SQLCompletionProvider.completions(
238 for: "m", cursorPosition: 1,
239 streamNames: streams, schemaFields: fields
240 )
241 XCTAssertTrue(result.items.contains(where: { $0.kind == .function })) // MAX, MIN
242 XCTAssertTrue(result.items.contains(where: { $0.kind == .table })) // metrics
243 XCTAssertTrue(result.items.contains(where: { $0.kind == .column })) // message
244 }
246 // MARK: - Completion item properties
248 func testCompletionItemKindLabels() {
249 let kw = SQLCompletionItem(text: "SELECT", kind: .keyword)
250 XCTAssertEqual(kw.kindLabel, "K")
252 let fn = SQLCompletionItem(text: "COUNT", kind: .function)
253 XCTAssertEqual(fn.kindLabel, "F")
255 let tbl = SQLCompletionItem(text: "logs", kind: .table)
256 XCTAssertEqual(tbl.kindLabel, "T")
258 let col = SQLCompletionItem(text: "level", kind: .column)
259 XCTAssertEqual(col.kindLabel, "C")
260 }
262 // MARK: - Context with DISTINCT, NOT, BETWEEN, etc.
264 func testContextAfterDistinct() {
265 let ctx = SQLCompletionProvider.determineContext(text: "SELECT DISTINCT ", position: 16)
266 XCTAssertEqual(ctx, .columnRef)
267 }
269 func testContextAfterNot() {
270 let ctx = SQLCompletionProvider.determineContext(text: "SELECT * FROM t WHERE NOT ", position: 26)
271 XCTAssertEqual(ctx, .columnRef)
272 }
274 func testContextAfterLeftJoin() {
275 let ctx = SQLCompletionProvider.determineContext(text: "SELECT * FROM a LEFT JOIN ", position: 26)
276 XCTAssertEqual(ctx, .tableRef)
277 }
280// Make SQLCompletionContext Equatable for assertions
281extension SQLCompletionContext: Equatable {}