test(addon-search): add regression tests for search state and edge cases
Add SearchState and SearchResultTracker unit tests plus whole-word, reverse-regex, and lowercase cache coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -256,6 +256,12 @@ describe('SearchEngine', () => {
|
||||
|
||||
assert.strictEqual(searchEngine.find('.*?', 0, 0, { regex: true }), undefined); // Empty matches should be ignored
|
||||
});
|
||||
|
||||
it('should ignore empty regex matches when searching backwards', async () => {
|
||||
await writeP(terminal, 'Hello World');
|
||||
|
||||
assert.strictEqual(searchEngine.findPreviousWithSelection('.*?', { regex: true }), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('combined options', () => {
|
||||
|
||||
@@ -244,6 +244,17 @@ describe('SearchLineCache', () => {
|
||||
assert.equal(retrieved![1].length, 4);
|
||||
});
|
||||
|
||||
it('should lazily cache lowercased line text via SearchEngine usage', async () => {
|
||||
await writeP(terminal, 'Hello WORLD');
|
||||
cache.initLinesCache();
|
||||
const entry = cache.translateBufferLineToStringWithWrap(0, true);
|
||||
cache.setLineInCache(0, entry);
|
||||
assert.equal(entry[2], undefined);
|
||||
entry[2] = entry[0].toLowerCase();
|
||||
cache.setLineInCache(0, entry);
|
||||
assert.strictEqual(cache.getLineFromCache(0)?.[2], 'hello world');
|
||||
});
|
||||
|
||||
it('should handle unicode characters in cache entries', () => {
|
||||
const entry: LineCacheEntry = [
|
||||
'Hello 世界 🌍 测试',
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { SearchResultTracker } from './SearchResultTracker';
|
||||
import type { ISearchResult } from './SearchEngine';
|
||||
|
||||
describe('SearchResultTracker', () => {
|
||||
let tracker: SearchResultTracker;
|
||||
|
||||
beforeEach(() => {
|
||||
tracker = new SearchResultTracker();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tracker.dispose();
|
||||
});
|
||||
|
||||
describe('updateResults', () => {
|
||||
it('should store results without copying when within limit', () => {
|
||||
const results: ISearchResult[] = [{ term: 'a', row: 0, col: 0, size: 1 }];
|
||||
tracker.updateResults(results, 10);
|
||||
assert.strictEqual(tracker.searchResults, results);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findResultIndex', () => {
|
||||
it('should return the index of a tracked result', () => {
|
||||
const results: ISearchResult[] = [
|
||||
{ term: 'a', row: 0, col: 0, size: 1 },
|
||||
{ term: 'b', row: 0, col: 2, size: 1 }
|
||||
];
|
||||
tracker.updateResults(results, 10);
|
||||
assert.strictEqual(tracker.findResultIndex(results[1]), 1);
|
||||
});
|
||||
|
||||
it('should return -1 when the result is not tracked', () => {
|
||||
tracker.updateResults([{ term: 'a', row: 0, col: 0, size: 1 }], 10);
|
||||
assert.strictEqual(tracker.findResultIndex({ term: 'z', row: 1, col: 0, size: 1 }), -1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fireResultsChanged', () => {
|
||||
it('should not fire when decorations are disabled', () => {
|
||||
let fired = false;
|
||||
tracker.onDidChangeResults(() => fired = true);
|
||||
tracker.updateResults([{ term: 'a', row: 0, col: 0, size: 1 }], 10);
|
||||
tracker.fireResultsChanged(false);
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { SearchState } from './SearchState';
|
||||
import type { ISearchOptions } from '@xterm/addon-search';
|
||||
|
||||
const DECORATIONS_A: ISearchOptions = {
|
||||
decorations: {
|
||||
activeMatchColorOverviewRuler: '#ff0000',
|
||||
matchOverviewRuler: '#00ffff'
|
||||
}
|
||||
};
|
||||
|
||||
const DECORATIONS_B: ISearchOptions = {
|
||||
decorations: {
|
||||
activeMatchColorOverviewRuler: '#00ff00',
|
||||
matchOverviewRuler: '#00ffff'
|
||||
}
|
||||
};
|
||||
|
||||
describe('SearchState', () => {
|
||||
let state: SearchState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = new SearchState();
|
||||
});
|
||||
|
||||
describe('didOptionsChange', () => {
|
||||
it('should detect caseSensitive changes', () => {
|
||||
state.lastSearchOptions = { caseSensitive: false };
|
||||
assert.strictEqual(state.didOptionsChange({ caseSensitive: true }), true);
|
||||
});
|
||||
|
||||
it('should detect decoration option changes', () => {
|
||||
state.lastSearchOptions = DECORATIONS_A;
|
||||
assert.strictEqual(state.didOptionsChange(DECORATIONS_B), true);
|
||||
});
|
||||
|
||||
it('should return false when semantic options are unchanged', () => {
|
||||
const options: ISearchOptions = { caseSensitive: false, regex: false, wholeWord: false };
|
||||
state.lastSearchOptions = options;
|
||||
assert.strictEqual(state.didOptionsChange({ caseSensitive: false, regex: false, wholeWord: false }), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldUpdateHighlighting', () => {
|
||||
it('should update when options change for the same term', () => {
|
||||
state.cachedSearchTerm = 'test';
|
||||
state.lastSearchOptions = { caseSensitive: false, decorations: DECORATIONS_A.decorations };
|
||||
assert.strictEqual(state.shouldUpdateHighlighting('test', { caseSensitive: true, decorations: DECORATIONS_A.decorations }), true);
|
||||
});
|
||||
|
||||
it('should not update without decorations', () => {
|
||||
state.cachedSearchTerm = 'test';
|
||||
state.lastSearchOptions = { caseSensitive: false };
|
||||
assert.strictEqual(state.shouldUpdateHighlighting('test', { caseSensitive: true }), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user