import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:xterm/buffer/buffer.dart'; import 'package:xterm/buffer/line/line.dart'; import 'package:xterm/terminal/terminal_search.dart'; import 'package:xterm/terminal/terminal_search_interaction.dart'; import 'package:xterm/util/circular_list.dart'; import 'terminal_search_test.mocks.dart'; class TerminalSearchTestCircularList extends CircularList { TerminalSearchTestCircularList(int maxLines) : super(maxLines); } @GenerateMocks([ TerminalSearchInteraction, Buffer, TerminalSearchTestCircularList, BufferLine ]) void main() { group('Terminal Search Tests', () { test('Creation works', () {}); test('Doesn\'t trigger anything when not activated', () { final fixture = _TestFixture(); verifyNoMoreInteractions(fixture.terminalSearchInteractionMock); final task = fixture.uut.createSearchTask('testsearch'); task.pattern = "some test"; task.isActive = false; task.searchResult; }); test('Basic search works', () { final fixture = _TestFixture(); fixture.expectTerminalSearchContent(['Simple Content']); final task = fixture.uut.createSearchTask('testsearch'); task.isActive = true; task.pattern = 'content'; task.options = TerminalSearchOptions( caseSensitive: false, matchWholeWord: false, useRegex: false); final result = task.searchResult; expect(result.allHits.length, 1); expect(result.allHits[0].startLineIndex, 0); expect(result.allHits[0].startIndex, 7); expect(result.allHits[0].endLineIndex, 0); expect(result.allHits[0].endIndex, 14); }); test('Multiline search works', () { final fixture = _TestFixture(); fixture.expectTerminalSearchContent(['Simple Content', 'Second Line']); final task = fixture.uut.createSearchTask('testsearch'); task.isActive = true; task.pattern = 'line'; task.options = TerminalSearchOptions( caseSensitive: false, matchWholeWord: false, useRegex: false); final result = task.searchResult; expect(result.allHits.length, 1); expect(result.allHits[0].startLineIndex, 1); expect(result.allHits[0].startIndex, 7); expect(result.allHits[0].endLineIndex, 1); expect(result.allHits[0].endIndex, 11); }); test('Finding strings directly on line break works', () { final fixture = _TestFixture(); fixture.expectTerminalSearchContent([ 'The search hit is '.padRight(fixture.terminalWidth - 3) + 'spl', 'it over two lines', ]); final task = fixture.uut.createSearchTask('testsearch'); task.isActive = true; task.pattern = 'split'; task.options = TerminalSearchOptions( caseSensitive: false, matchWholeWord: false, useRegex: false); final result = task.searchResult; expect(result.allHits.length, 1); expect(result.allHits[0].startLineIndex, 0); expect(result.allHits[0].startIndex, 77); expect(result.allHits[0].endLineIndex, 1); expect(result.allHits[0].endIndex, 2); }); }); test('Option: case sensitivity works', () { final fixture = _TestFixture(); fixture.expectTerminalSearchContent(['Simple Content', 'Second Line']); final task = fixture.uut.createSearchTask('testsearch'); task.isActive = true; task.pattern = 'line'; task.options = TerminalSearchOptions( caseSensitive: true, matchWholeWord: false, useRegex: false); final result = task.searchResult; expect(result.allHits.length, 0); task.pattern = 'Line'; final secondResult = task.searchResult; expect(secondResult.allHits.length, 1); expect(secondResult.allHits[0].startLineIndex, 1); expect(secondResult.allHits[0].startIndex, 7); expect(secondResult.allHits[0].endLineIndex, 1); expect(secondResult.allHits[0].endIndex, 11); }); test('Option: whole word works', () { final fixture = _TestFixture(); fixture.expectTerminalSearchContent(['Simple Content', 'Second Line']); final task = fixture.uut.createSearchTask('testsearch'); task.isActive = true; task.pattern = 'lin'; task.options = TerminalSearchOptions( caseSensitive: false, matchWholeWord: true, useRegex: false); final result = task.searchResult; expect(result.allHits.length, 0); task.pattern = 'line'; final secondResult = task.searchResult; expect(secondResult.allHits.length, 1); expect(secondResult.allHits[0].startLineIndex, 1); expect(secondResult.allHits[0].startIndex, 7); expect(secondResult.allHits[0].endLineIndex, 1); expect(secondResult.allHits[0].endIndex, 11); }); test('Option: regex works', () { final fixture = _TestFixture(); fixture.expectTerminalSearchContent(['Simple Content', 'Second Line']); final task = fixture.uut.createSearchTask('testsearch'); task.isActive = true; task.options = TerminalSearchOptions( caseSensitive: false, matchWholeWord: false, useRegex: true); task.pattern = r'(^|\s)\w{4}($|\s)'; // match exactly 4 characters (and the whitespace before and/or after) final secondResult = task.searchResult; expect(secondResult.allHits.length, 1); expect(secondResult.allHits[0].startLineIndex, 1); expect(secondResult.allHits[0].startIndex, 6); expect(secondResult.allHits[0].endLineIndex, 1); expect(secondResult.allHits[0].endIndex, 12); }); test('Retrigger search when a BufferLine got dirty works', () { final fixture = _TestFixture(); fixture.expectTerminalSearchContent( ['Simple Content', 'Second Line', 'Third row']); final task = fixture.uut.createSearchTask('testsearch'); task.isActive = true; task.options = TerminalSearchOptions( caseSensitive: false, matchWholeWord: false, useRegex: false); task.pattern = 'line'; final result = task.searchResult; expect(result.allHits.length, 1); // overwrite expectations, nothing dirty => no new search fixture.expectTerminalSearchContent( ['Simple Content', 'Second Line', 'Third line'], isSearchStringCached: false); final secondResult = task.searchResult; expect(secondResult.allHits.length, 1); // nothing was dirty => we get the cached search result // overwrite expectations, one line is dirty => new search fixture.expectTerminalSearchContent( ['Simple Content', 'Second Line', 'Third line'], isSearchStringCached: false, dirtyIndices: [1]); final thirdResult = task.searchResult; expect(thirdResult.allHits.length, 2); //search has happened again so the new content is found // overwrite expectations, content has changed => new search fixture.expectTerminalSearchContent( ['First line', 'Second Line', 'Third line'], isSearchStringCached: false, dirtyIndices: [0]); final fourthResult = task.searchResult; expect(fourthResult.allHits.length, 3); //search has happened again so the new content is found }); } class _TestFixture { _TestFixture({ this.terminalWidth = 80, }) { uut = TerminalSearch(terminalSearchInteractionMock); when(terminalSearchInteractionMock.terminalWidth).thenReturn(terminalWidth); } final int terminalWidth; void expectTerminalSearchContent( List lines, { isUsingAltBuffer = false, isSearchStringCached = true, List? dirtyIndices, }) { final buffer = _getBuffer(lines, isCached: isSearchStringCached, dirtyIndices: dirtyIndices); when(terminalSearchInteractionMock.buffer).thenReturn(buffer); when(terminalSearchInteractionMock.isUsingAltBuffer()) .thenReturn(isUsingAltBuffer); } final terminalSearchInteractionMock = MockTerminalSearchInteraction(); late final TerminalSearch uut; MockBuffer _getBuffer( List lines, { isCached = true, List? dirtyIndices, }) { final result = MockBuffer(); final circularList = MockTerminalSearchTestCircularList(); when(result.lines).thenReturn(circularList); final bufferLines = _getBufferLinesWithSearchContent( lines, isCached: isCached, dirtyIndices: dirtyIndices, ); when(circularList[any]).thenAnswer( (realInvocation) => bufferLines[realInvocation.positionalArguments[0]]); when(circularList.length).thenReturn(bufferLines.length); return result; } List _getBufferLinesWithSearchContent( List content, { isCached = true, List? dirtyIndices, }) { final result = List.empty(growable: true); for (int i = 0; i < content.length; i++) { final bl = MockBufferLine(); when(bl.hasCachedSearchString).thenReturn(isCached); when(bl.toSearchString(any)).thenReturn(content[i]); if (dirtyIndices?.contains(i) ?? false) { when(bl.isTagDirty(any)).thenReturn(true); } else { when(bl.isTagDirty(any)).thenReturn(false); } result.add(bl); } return result; } }