Skip to content

Commit 8a5017d

Browse files
committed
Add more xterm.js tests
1 parent 29e8688 commit 8a5017d

1 file changed

Lines changed: 319 additions & 0 deletions

File tree

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
import Foundation
2+
import Testing
3+
4+
@testable import SwiftTerm
5+
6+
final class SearchTests {
7+
private func makeTerminal(cols: Int = 80, rows: Int = 24) -> Terminal {
8+
let (terminal, _) = TerminalTestHarness.makeTerminal(cols: cols, rows: rows)
9+
return terminal
10+
}
11+
12+
// MARK: - SearchLineCache
13+
14+
@Test func testSearchLineCacheStartsEmpty() {
15+
let terminal = makeTerminal()
16+
let cache = SearchLineCache(terminal: terminal)
17+
18+
#expect(cache.getLineFromCache(row: 0) == nil)
19+
}
20+
21+
@Test func testSearchLineCacheInitAndSetGet() {
22+
let terminal = makeTerminal()
23+
let cache = SearchLineCache(terminal: terminal)
24+
25+
cache.setLineInCache(row: 0, entry: ("test", [0]))
26+
#expect(cache.getLineFromCache(row: 0) == nil)
27+
28+
cache.initLinesCache()
29+
cache.setLineInCache(row: 0, entry: ("test", [0]))
30+
#expect(cache.getLineFromCache(row: 0)?.lineAsString == "test")
31+
}
32+
33+
@Test func testSearchLineCacheInitDoesNotDropEntries() {
34+
let terminal = makeTerminal()
35+
let cache = SearchLineCache(terminal: terminal)
36+
37+
cache.initLinesCache()
38+
cache.setLineInCache(row: 0, entry: ("test", [0]))
39+
cache.initLinesCache()
40+
41+
#expect(cache.getLineFromCache(row: 0)?.lineAsString == "test")
42+
}
43+
44+
@Test func testTranslateBufferLineToStringWithWrapBasic() {
45+
let terminal = makeTerminal()
46+
let cache = SearchLineCache(terminal: terminal)
47+
48+
terminal.feed(text: "Hello World")
49+
let result = cache.translateBufferLineToStringWithWrap(lineIndex: 0, trimRight: true)
50+
51+
#expect(result.lineAsString == "Hello World")
52+
#expect(result.lineOffsets == [0])
53+
}
54+
55+
@Test func testTranslateBufferLineToStringWithWrapTrimRight() {
56+
let terminal = makeTerminal()
57+
let cache = SearchLineCache(terminal: terminal)
58+
59+
terminal.feed(text: "Hello World ")
60+
let trimmed = cache.translateBufferLineToStringWithWrap(lineIndex: 0, trimRight: true)
61+
let untrimmed = cache.translateBufferLineToStringWithWrap(lineIndex: 0, trimRight: false)
62+
63+
#expect(trimmed.lineAsString.hasPrefix("Hello World"))
64+
#expect(untrimmed.lineAsString.count >= trimmed.lineAsString.count)
65+
}
66+
67+
@Test func testTranslateBufferLineToStringWithWrapWrapped() {
68+
let terminal = makeTerminal(cols: 80, rows: 24)
69+
let cache = SearchLineCache(terminal: terminal)
70+
71+
let longText = String(repeating: "A", count: 200)
72+
terminal.feed(text: longText)
73+
74+
let result = cache.translateBufferLineToStringWithWrap(lineIndex: 0, trimRight: true)
75+
#expect(result.lineAsString == longText)
76+
#expect(result.lineOffsets.count > 1)
77+
#expect(result.lineOffsets.first == 0)
78+
}
79+
80+
@Test func testTranslateBufferLineToStringWithWrapWideCharacters() {
81+
let terminal = makeTerminal()
82+
let cache = SearchLineCache(terminal: terminal)
83+
84+
terminal.feed(text: "Hello 世界")
85+
let result = cache.translateBufferLineToStringWithWrap(lineIndex: 0, trimRight: true)
86+
87+
#expect(result.lineAsString == "Hello 世界")
88+
#expect(result.lineOffsets == [0])
89+
}
90+
91+
@Test func testTranslateBufferLineToStringWithWrapOutOfRange() {
92+
let terminal = makeTerminal()
93+
let cache = SearchLineCache(terminal: terminal)
94+
95+
let result = cache.translateBufferLineToStringWithWrap(lineIndex: 1000, trimRight: true)
96+
#expect(result.lineAsString == "")
97+
#expect(result.lineOffsets == [0])
98+
}
99+
100+
// MARK: - SearchEngine
101+
102+
@Test func testSearchEngineFindBasic() {
103+
let terminal = makeTerminal()
104+
let cache = SearchLineCache(terminal: terminal)
105+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
106+
107+
terminal.feed(text: "Hello World")
108+
109+
#expect(engine.find(term: "", startRow: 0, startCol: 0) == nil)
110+
#expect(engine.find(term: "World", startRow: 0, startCol: 0) == SearchResult(term: "World", col: 6, row: 0, size: 5))
111+
}
112+
113+
@Test func testSearchEngineFindFromPosition() {
114+
let terminal = makeTerminal()
115+
let cache = SearchLineCache(terminal: terminal)
116+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
117+
118+
terminal.feed(text: "Hello Hello Hello")
119+
120+
#expect(engine.find(term: "Hello", startRow: 0, startCol: 7) == SearchResult(term: "Hello", col: 12, row: 0, size: 5))
121+
}
122+
123+
@Test func testSearchEngineFindAcrossRows() {
124+
let terminal = makeTerminal()
125+
let cache = SearchLineCache(terminal: terminal)
126+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
127+
128+
terminal.feed(text: "Line 1\r\nLine 2 target\r\nLine 3")
129+
130+
#expect(engine.find(term: "target", startRow: 0, startCol: 0) == SearchResult(term: "target", col: 7, row: 1, size: 6))
131+
}
132+
133+
@Test func testSearchEngineFindNotFoundAndInvalidCol() {
134+
let terminal = makeTerminal(cols: 80, rows: 24)
135+
let cache = SearchLineCache(terminal: terminal)
136+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
137+
138+
terminal.feed(text: "Hello World")
139+
140+
#expect(engine.find(term: "NotFound", startRow: 0, startCol: 0) == nil)
141+
#expect(engine.find(term: "Hello", startRow: 0, startCol: 100) == nil)
142+
#expect(engine.find(term: "Hello", startRow: 0, startCol: 79) == nil)
143+
}
144+
145+
@Test func testSearchEngineFindFromMiddleDoesNotMatchBeforeOffset() {
146+
let terminal = makeTerminal()
147+
let cache = SearchLineCache(terminal: terminal)
148+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
149+
150+
terminal.feed(text: "Hello World")
151+
152+
#expect(engine.find(term: "llo", startRow: 0, startCol: 3) == nil)
153+
}
154+
155+
@Test func testSearchEngineCaseSensitivity() {
156+
let terminal = makeTerminal()
157+
let cache = SearchLineCache(terminal: terminal)
158+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
159+
160+
terminal.feed(text: "Hello WORLD")
161+
162+
#expect(engine.find(term: "world", startRow: 0, startCol: 0) == SearchResult(term: "world", col: 6, row: 0, size: 5))
163+
#expect(engine.find(term: "WORLD", startRow: 0, startCol: 0, searchOptions: SearchOptions(caseSensitive: true)) == SearchResult(term: "WORLD", col: 6, row: 0, size: 5))
164+
#expect(engine.find(term: "world", startRow: 0, startCol: 0, searchOptions: SearchOptions(caseSensitive: true)) == nil)
165+
}
166+
167+
@Test func testSearchEngineWholeWord() {
168+
let terminal = makeTerminal()
169+
let cache = SearchLineCache(terminal: terminal)
170+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
171+
172+
terminal.feed(text: "Hello world wonderful")
173+
174+
#expect(engine.find(term: "world", startRow: 0, startCol: 0, searchOptions: SearchOptions(wholeWord: true)) == SearchResult(term: "world", col: 6, row: 0, size: 5))
175+
#expect(engine.find(term: "wonder", startRow: 0, startCol: 0, searchOptions: SearchOptions(wholeWord: true)) == nil)
176+
}
177+
178+
@Test func testSearchEngineWholeWordBoundaries() {
179+
let terminal = makeTerminal()
180+
let cache = SearchLineCache(terminal: terminal)
181+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
182+
183+
terminal.feed(text: "hello,world!test")
184+
185+
#expect(engine.find(term: "world", startRow: 0, startCol: 0, searchOptions: SearchOptions(wholeWord: true)) == SearchResult(term: "world", col: 6, row: 0, size: 5))
186+
}
187+
188+
@Test func testSearchEngineRegex() {
189+
let terminal = makeTerminal()
190+
let cache = SearchLineCache(terminal: terminal)
191+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
192+
193+
terminal.feed(text: "Hello 123 World")
194+
195+
#expect(engine.find(term: "[0-9]+", startRow: 0, startCol: 0, searchOptions: SearchOptions(regex: true)) == SearchResult(term: "123", col: 6, row: 0, size: 3))
196+
#expect(engine.find(term: "[invalid", startRow: 0, startCol: 0, searchOptions: SearchOptions(regex: true)) == nil)
197+
#expect(engine.find(term: ".*?", startRow: 0, startCol: 0, searchOptions: SearchOptions(regex: true)) == nil)
198+
}
199+
200+
@Test func testSearchEngineCombinedOptions() {
201+
let terminal = makeTerminal()
202+
let cache = SearchLineCache(terminal: terminal)
203+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
204+
205+
terminal.feed(text: "Hello WORLD world")
206+
207+
#expect(engine.find(term: "[A-Z]+", startRow: 0, startCol: 0, searchOptions: SearchOptions(caseSensitive: true, regex: true)) == SearchResult(term: "H", col: 0, row: 0, size: 1))
208+
#expect(engine.find(term: "WORLD", startRow: 0, startCol: 0, searchOptions: SearchOptions(caseSensitive: true, wholeWord: true)) == SearchResult(term: "WORLD", col: 6, row: 0, size: 5))
209+
}
210+
211+
@Test func testSearchEngineWrappedLines() {
212+
let terminal = makeTerminal(cols: 80, rows: 24)
213+
let cache = SearchLineCache(terminal: terminal)
214+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
215+
216+
let longText = String(repeating: "A", count: 100) + "target" + String(repeating: "B", count: 50)
217+
terminal.feed(text: longText)
218+
219+
#expect(engine.find(term: "target", startRow: 0, startCol: 0) == SearchResult(term: "target", col: 20, row: 1, size: 6))
220+
}
221+
222+
@Test func testSearchEngineUnicode() {
223+
let terminal = makeTerminal()
224+
let cache = SearchLineCache(terminal: terminal)
225+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
226+
227+
terminal.feed(text: "Hello 世界 World")
228+
229+
#expect(engine.find(term: "世界", startRow: 0, startCol: 0) == SearchResult(term: "世界", col: 6, row: 0, size: 4))
230+
}
231+
232+
@Test func testSearchEngineBufferBoundaries() {
233+
let terminal = makeTerminal()
234+
let cache = SearchLineCache(terminal: terminal)
235+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
236+
237+
#expect(engine.find(term: "anything", startRow: 0, startCol: 0) == nil)
238+
#expect(engine.find(term: "test", startRow: 1000, startCol: 0) == nil)
239+
}
240+
241+
// MARK: - SearchEngine selection helpers
242+
243+
@Test func testSearchEngineFindNextWithSelection() {
244+
let terminal = makeTerminal()
245+
let cache = SearchLineCache(terminal: terminal)
246+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
247+
248+
terminal.feed(text: "Hello World Hello")
249+
250+
#expect(engine.findNextWithSelection(term: "", cachedSearchTerm: nil, previousSelection: nil) == nil)
251+
#expect(engine.findNextWithSelection(term: "Hello", cachedSearchTerm: nil, previousSelection: nil) == SearchResult(term: "Hello", col: 0, row: 0, size: 5))
252+
253+
let selection = SearchSelection(start: Position(col: 0, row: 0), end: Position(col: 5, row: 0))
254+
#expect(engine.findNextWithSelection(term: "Hello", cachedSearchTerm: "Hello", previousSelection: selection) == SearchResult(term: "Hello", col: 12, row: 0, size: 5))
255+
256+
let selectionAtEnd = SearchSelection(start: Position(col: 12, row: 0), end: Position(col: 17, row: 0))
257+
#expect(engine.findNextWithSelection(term: "Hello", cachedSearchTerm: "Hello", previousSelection: selectionAtEnd) == SearchResult(term: "Hello", col: 0, row: 0, size: 5))
258+
}
259+
260+
@Test func testSearchEngineFindPreviousWithSelection() {
261+
let terminal = makeTerminal()
262+
let cache = SearchLineCache(terminal: terminal)
263+
let engine = SearchEngine(terminal: terminal, lineCache: cache)
264+
265+
terminal.feed(text: "Hello World Hello")
266+
267+
#expect(engine.findPreviousWithSelection(term: "", cachedSearchTerm: nil, previousSelection: nil) == nil)
268+
#expect(engine.findPreviousWithSelection(term: "Hello", cachedSearchTerm: nil, previousSelection: nil) == SearchResult(term: "Hello", col: 12, row: 0, size: 5))
269+
270+
let selectionAtEnd = SearchSelection(start: Position(col: 12, row: 0), end: Position(col: 17, row: 0))
271+
let previous = engine.findPreviousWithSelection(term: "Hello", cachedSearchTerm: "Hello", previousSelection: selectionAtEnd)
272+
#expect(previous != nil)
273+
#expect(previous?.row == 0)
274+
}
275+
276+
// MARK: - SearchService
277+
278+
@Test func testSearchServiceFindAllAndLimit() {
279+
let terminal = makeTerminal()
280+
let service = SearchService(terminal: terminal)
281+
282+
terminal.feed(text: "Hello Hello Hello")
283+
284+
let results = service.findAll(term: "Hello")
285+
#expect(results.count == 3)
286+
#expect(results[0].col == 0)
287+
#expect(results[1].col == 6)
288+
#expect(results[2].col == 12)
289+
290+
let limited = service.findAll(term: "Hello", limit: 2)
291+
#expect(limited.count == 2)
292+
}
293+
294+
@Test func testSearchServiceSelectionRange() {
295+
let terminal = makeTerminal(cols: 10, rows: 2)
296+
let service = SearchService(terminal: terminal)
297+
298+
let result = SearchResult(term: "Hello", col: 8, row: 0, size: 4)
299+
let range = service.selectionRange(for: result)
300+
301+
#expect(range.start == Position(col: 8, row: 0))
302+
#expect(range.end == Position(col: 2, row: 1))
303+
}
304+
305+
@Test func testSearchServiceFindNextUpdatesLastResult() {
306+
let terminal = makeTerminal()
307+
let service = SearchService(terminal: terminal)
308+
309+
terminal.feed(text: "Hello World")
310+
311+
#expect(service.lastResult == nil)
312+
let result = service.findNext(term: "World")
313+
#expect(result == SearchResult(term: "World", col: 6, row: 0, size: 5))
314+
#expect(service.lastResult == result)
315+
316+
_ = service.findNext(term: "")
317+
#expect(service.lastResult == nil)
318+
}
319+
}

0 commit comments

Comments
 (0)